@gxp-dev/tools 2.0.71 → 2.0.73
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 +108 -81
- package/bin/lib/cli.js +18 -0
- package/bin/lib/commands/index.js +2 -0
- package/bin/lib/commands/init.js +104 -82
- package/bin/lib/commands/lint.js +77 -0
- package/bin/lib/constants.js +12 -0
- package/bin/lib/lint/formatter.js +91 -0
- package/bin/lib/lint/index.js +284 -0
- package/bin/lib/lint/schemas/app-manifest.schema.json +124 -0
- package/bin/lib/lint/schemas/card.schema.json +165 -0
- package/bin/lib/lint/schemas/common.schema.json +62 -0
- package/bin/lib/lint/schemas/configuration.schema.json +19 -0
- package/bin/lib/lint/schemas/field.schema.json +230 -0
- package/bin/lib/utils/ai-scaffold.js +137 -0
- package/mcp/gxp-api-server.js +87 -129
- package/mcp/lib/api-tools.js +543 -0
- package/mcp/lib/config-ops.js +234 -0
- package/mcp/lib/config-tools.js +549 -0
- package/mcp/lib/docs-tools.js +142 -0
- package/mcp/lib/docs.js +263 -0
- package/mcp/lib/specs.js +135 -0
- package/mcp/lib/test-tools.js +358 -0
- package/package.json +3 -1
- package/runtime/stores/gxpPortalConfigStore.js +88 -87
- package/runtime/vite.config.js +5 -3
- package/template/.claude/agents/gxp-developer.md +377 -50
- package/template/.prettierrc +10 -0
- package/template/AGENTS.md +265 -21
- package/template/GEMINI.md +181 -19
- package/template/README.md +205 -240
- package/template/app-instructions.md +91 -0
- package/template/eslint.config.js +32 -0
- package/template/githooks/pre-commit +37 -0
package/mcp/gxp-api-server.js
CHANGED
|
@@ -18,130 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const readline = require("readline")
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
apiBaseUrl: "https://api.gramercy.cloud",
|
|
28
|
-
openApiSpec: "https://api.gramercy.cloud/api-specs/openapi.json",
|
|
29
|
-
asyncApiSpec: "https://api.gramercy.cloud/api-specs/asyncapi.json",
|
|
30
|
-
webhookSpec: "https://api.gramercy.cloud/api-specs/webhooks.json",
|
|
31
|
-
},
|
|
32
|
-
staging: {
|
|
33
|
-
apiBaseUrl: "https://api.efz-staging.env.eventfinity.app",
|
|
34
|
-
openApiSpec:
|
|
35
|
-
"https://api.efz-staging.env.eventfinity.app/api-specs/openapi.json",
|
|
36
|
-
asyncApiSpec:
|
|
37
|
-
"https://api.efz-staging.env.eventfinity.app/api-specs/asyncapi.json",
|
|
38
|
-
webhookSpec:
|
|
39
|
-
"https://api.efz-staging.env.eventfinity.app/api-specs/webhooks.json",
|
|
40
|
-
},
|
|
41
|
-
testing: {
|
|
42
|
-
apiBaseUrl: "https://api.zenith-develop-testing.env.eventfinity.app",
|
|
43
|
-
openApiSpec:
|
|
44
|
-
"https://api.zenith-develop-testing.env.eventfinity.app/api-specs/openapi.json",
|
|
45
|
-
asyncApiSpec:
|
|
46
|
-
"https://api.zenith-develop-testing.env.eventfinity.app/api-specs/asyncapi.json",
|
|
47
|
-
webhookSpec:
|
|
48
|
-
"https://api.zenith-develop-testing.env.eventfinity.app/api-specs/webhooks.json",
|
|
49
|
-
},
|
|
50
|
-
develop: {
|
|
51
|
-
apiBaseUrl: "https://api.zenith-develop.env.eventfinity.app",
|
|
52
|
-
openApiSpec:
|
|
53
|
-
"https://api.zenith-develop.env.eventfinity.app/api-specs/openapi.json",
|
|
54
|
-
asyncApiSpec:
|
|
55
|
-
"https://api.zenith-develop.env.eventfinity.app/api-specs/asyncapi.json",
|
|
56
|
-
webhookSpec:
|
|
57
|
-
"https://api.zenith-develop.env.eventfinity.app/api-specs/webhooks.json",
|
|
58
|
-
},
|
|
59
|
-
local: {
|
|
60
|
-
apiBaseUrl: "https://dashboard.eventfinity.test",
|
|
61
|
-
openApiSpec: "https://api.eventfinity.test/api-specs/openapi.json",
|
|
62
|
-
asyncApiSpec: "https://api.eventfinity.test/api-specs/asyncapi.json",
|
|
63
|
-
webhookSpec: "https://api.eventfinity.test/api-specs/webhooks.json",
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Cache for fetched specs
|
|
68
|
-
const specCache = {
|
|
69
|
-
openapi: null,
|
|
70
|
-
asyncapi: null,
|
|
71
|
-
webhooks: null,
|
|
72
|
-
lastFetch: null,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get current environment from .env file or default
|
|
79
|
-
*/
|
|
80
|
-
function getEnvironment() {
|
|
81
|
-
// Try to read from .env file in current directory
|
|
82
|
-
const envPath = path.join(process.cwd(), ".env")
|
|
83
|
-
if (fs.existsSync(envPath)) {
|
|
84
|
-
const envContent = fs.readFileSync(envPath, "utf-8")
|
|
85
|
-
const match = envContent.match(/VITE_API_ENV=(\w+)/)
|
|
86
|
-
if (match) {
|
|
87
|
-
return match[1]
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Fall back to environment variable or default
|
|
92
|
-
return process.env.VITE_API_ENV || process.env.API_ENV || "develop"
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get URLs for current environment
|
|
97
|
-
*/
|
|
98
|
-
function getEnvUrls() {
|
|
99
|
-
const env = getEnvironment()
|
|
100
|
-
return ENVIRONMENT_URLS[env] || ENVIRONMENT_URLS.develop
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Fetch a spec with caching
|
|
105
|
-
*/
|
|
106
|
-
async function fetchSpec(specType) {
|
|
107
|
-
const urls = getEnvUrls()
|
|
108
|
-
const urlMap = {
|
|
109
|
-
openapi: urls.openApiSpec,
|
|
110
|
-
asyncapi: urls.asyncApiSpec,
|
|
111
|
-
webhooks: urls.webhookSpec,
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const url = urlMap[specType]
|
|
115
|
-
if (!url) {
|
|
116
|
-
throw new Error(`Unknown spec type: ${specType}`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check cache
|
|
120
|
-
const now = Date.now()
|
|
121
|
-
if (
|
|
122
|
-
specCache[specType] &&
|
|
123
|
-
specCache.lastFetch &&
|
|
124
|
-
now - specCache.lastFetch < CACHE_TTL
|
|
125
|
-
) {
|
|
126
|
-
return specCache[specType]
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Fetch fresh
|
|
130
|
-
try {
|
|
131
|
-
const response = await fetch(url)
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
134
|
-
}
|
|
135
|
-
const data = await response.json()
|
|
136
|
-
specCache[specType] = data
|
|
137
|
-
specCache.lastFetch = now
|
|
138
|
-
return data
|
|
139
|
-
} catch (error) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
`Failed to fetch ${specType} spec from ${url}: ${error.message}`,
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
21
|
+
const {
|
|
22
|
+
ENVIRONMENT_URLS,
|
|
23
|
+
getEnvironment,
|
|
24
|
+
getEnvUrls,
|
|
25
|
+
fetchSpec,
|
|
26
|
+
} = require("./lib/specs")
|
|
145
27
|
|
|
146
28
|
/**
|
|
147
29
|
* Search OpenAPI spec for endpoints matching a query
|
|
@@ -181,12 +63,40 @@ function searchEndpoints(spec, query) {
|
|
|
181
63
|
}
|
|
182
64
|
|
|
183
65
|
/**
|
|
184
|
-
* Search AsyncAPI spec for channels/events matching a query
|
|
66
|
+
* Search AsyncAPI spec for channels/events matching a query.
|
|
67
|
+
*
|
|
68
|
+
* Matches across:
|
|
69
|
+
* - components.messages (event name, summary, description, x-triggered-by)
|
|
70
|
+
* - channels (channel name, description)
|
|
71
|
+
*
|
|
72
|
+
* For messages, the returned `eventName` is what you pass to
|
|
73
|
+
* store.listen(eventName, permissionIdentifier, callback) on the client.
|
|
185
74
|
*/
|
|
186
75
|
function searchEvents(spec, query) {
|
|
187
76
|
const results = []
|
|
188
77
|
const queryLower = query.toLowerCase()
|
|
189
78
|
|
|
79
|
+
const messages = spec?.components?.messages || {}
|
|
80
|
+
for (const [eventName, message] of Object.entries(messages)) {
|
|
81
|
+
if (typeof message !== "object" || message === null) continue
|
|
82
|
+
const trigger = message["x-triggered-by"] || ""
|
|
83
|
+
if (
|
|
84
|
+
eventName.toLowerCase().includes(queryLower) ||
|
|
85
|
+
message.summary?.toLowerCase().includes(queryLower) ||
|
|
86
|
+
message.description?.toLowerCase().includes(queryLower) ||
|
|
87
|
+
trigger.toLowerCase().includes(queryLower)
|
|
88
|
+
) {
|
|
89
|
+
results.push({
|
|
90
|
+
kind: "event",
|
|
91
|
+
eventName,
|
|
92
|
+
summary: message.summary || "",
|
|
93
|
+
description: message.description || "",
|
|
94
|
+
triggeredBy: trigger || null,
|
|
95
|
+
payloadRef: message.payload?.$ref || null,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
190
100
|
if (spec.channels) {
|
|
191
101
|
for (const [channel, details] of Object.entries(spec.channels)) {
|
|
192
102
|
if (
|
|
@@ -210,6 +120,7 @@ function searchEvents(spec, query) {
|
|
|
210
120
|
}
|
|
211
121
|
|
|
212
122
|
results.push({
|
|
123
|
+
kind: "channel",
|
|
213
124
|
channel,
|
|
214
125
|
description: details.description || "",
|
|
215
126
|
operations,
|
|
@@ -247,13 +158,38 @@ function getEndpointDetails(spec, path, method) {
|
|
|
247
158
|
}
|
|
248
159
|
|
|
249
160
|
// MCP Server Implementation
|
|
161
|
+
const {
|
|
162
|
+
CONFIG_TOOLS,
|
|
163
|
+
handleConfigToolCall,
|
|
164
|
+
isConfigTool,
|
|
165
|
+
} = require("./lib/config-tools")
|
|
166
|
+
|
|
167
|
+
const {
|
|
168
|
+
EXT_API_TOOLS,
|
|
169
|
+
handleExtApiToolCall,
|
|
170
|
+
isExtApiTool,
|
|
171
|
+
} = require("./lib/api-tools")
|
|
172
|
+
|
|
173
|
+
const {
|
|
174
|
+
DOCS_TOOLS,
|
|
175
|
+
handleDocsToolCall,
|
|
176
|
+
isDocsTool,
|
|
177
|
+
} = require("./lib/docs-tools")
|
|
178
|
+
|
|
179
|
+
const {
|
|
180
|
+
TEST_TOOLS,
|
|
181
|
+
handleTestToolCall,
|
|
182
|
+
isTestTool,
|
|
183
|
+
} = require("./lib/test-tools")
|
|
184
|
+
|
|
250
185
|
const SERVER_INFO = {
|
|
251
186
|
name: "gxp-api-server",
|
|
252
|
-
version: "
|
|
253
|
-
description:
|
|
187
|
+
version: "2.0.0",
|
|
188
|
+
description:
|
|
189
|
+
"GxP toolkit MCP server: API specs, config/manifest editing, documentation search, and plugin test helpers for AI coding assistants.",
|
|
254
190
|
}
|
|
255
191
|
|
|
256
|
-
const
|
|
192
|
+
const API_TOOLS = [
|
|
257
193
|
{
|
|
258
194
|
name: "get_openapi_spec",
|
|
259
195
|
description:
|
|
@@ -293,7 +229,7 @@ const TOOLS = [
|
|
|
293
229
|
{
|
|
294
230
|
name: "search_websocket_events",
|
|
295
231
|
description:
|
|
296
|
-
"Search
|
|
232
|
+
"Search AsyncAPI events matching a query. Searches components.messages (event name, summary, description, x-triggered-by) and channel definitions. The returned eventName is what you pass to store.listen(eventName, permissionIdentifier, callback).",
|
|
297
233
|
inputSchema: {
|
|
298
234
|
type: "object",
|
|
299
235
|
properties: {
|
|
@@ -337,10 +273,32 @@ const TOOLS = [
|
|
|
337
273
|
},
|
|
338
274
|
]
|
|
339
275
|
|
|
276
|
+
// Final tool set surfaced to MCP clients: API spec tools + extended API tools
|
|
277
|
+
// + config-editor tools + doc-search tools + test tools.
|
|
278
|
+
const TOOLS = [
|
|
279
|
+
...API_TOOLS,
|
|
280
|
+
...EXT_API_TOOLS,
|
|
281
|
+
...CONFIG_TOOLS,
|
|
282
|
+
...DOCS_TOOLS,
|
|
283
|
+
...TEST_TOOLS,
|
|
284
|
+
]
|
|
285
|
+
|
|
340
286
|
/**
|
|
341
287
|
* Handle MCP tool calls
|
|
342
288
|
*/
|
|
343
289
|
async function handleToolCall(name, args) {
|
|
290
|
+
if (isConfigTool(name)) {
|
|
291
|
+
return handleConfigToolCall(name, args)
|
|
292
|
+
}
|
|
293
|
+
if (isExtApiTool(name)) {
|
|
294
|
+
return handleExtApiToolCall(name, args)
|
|
295
|
+
}
|
|
296
|
+
if (isDocsTool(name)) {
|
|
297
|
+
return handleDocsToolCall(name, args)
|
|
298
|
+
}
|
|
299
|
+
if (isTestTool(name)) {
|
|
300
|
+
return handleTestToolCall(name, args)
|
|
301
|
+
}
|
|
344
302
|
switch (name) {
|
|
345
303
|
case "get_openapi_spec": {
|
|
346
304
|
const spec = await fetchSpec("openapi")
|