@gxp-dev/tools 2.0.81 → 2.0.83
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 +7 -3
- package/bin/lib/cli.js +4 -2
- package/bin/lib/commands/init.js +3 -2
- package/bin/lib/utils/ai-scaffold.js +10 -8
- package/mcp/gxp-api-server.js +19 -475
- package/mcp/lib/model-tools.js +192 -0
- package/mcp/lib/server.js +391 -0
- package/mcp/lib/uikit-tools.js +181 -0
- package/mcp/mcp-serve.js +25 -0
- package/package.json +3 -1
- package/runtime/vite.config.js +1 -1
- package/template/.claude/agents/gxp-developer.md +1 -1
- package/template/AGENTS.md +1 -1
- package/template/GEMINI.md +1 -1
- package/template/gemini/settings.json +1 -1
- package/template/mcp.json +1 -1
package/README.md
CHANGED
|
@@ -93,20 +93,24 @@ It does **not** overwrite your source files (`src/`, `theme-layouts/`, etc.).
|
|
|
93
93
|
- **Config linting** — AJV-based JSON Schema validation of `configuration.json` (form-builder definitions) and `app-manifest.json` (plugin metadata + defaults).
|
|
94
94
|
- **Pre-commit hook** — `.githooks/pre-commit` runs Prettier, ESLint, and the GxP linter on staged files; configured automatically via the `prepare` npm script.
|
|
95
95
|
- **Unit testing** — Vitest + `@vue/test-utils` wired out of the box; scaffolded tests via the MCP server.
|
|
96
|
-
- **MCP server** —
|
|
96
|
+
- **MCP server** — 33 tools for AI coding assistants (see below).
|
|
97
97
|
- **AI scaffolding** — pre-wired configs for Claude Code, Codex, and Gemini during `init`.
|
|
98
98
|
|
|
99
99
|
## MCP Server for AI assistants
|
|
100
100
|
|
|
101
|
-
The toolkit ships `
|
|
101
|
+
The toolkit ships `mcp-serve` (bin `@gxp-dev/tools/mcp/mcp-serve.js`), an MCP server exposing 33 tools across seven families. It speaks MCP over stdio via the official `@modelcontextprotocol/sdk` `StdioServerTransport`. Point your AI assistant at it to get API-aware, schema-aware, test-aware help inside plugin projects:
|
|
102
102
|
|
|
103
103
|
| Family | Tools |
|
|
104
104
|
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
105
|
| **API spec** (6) | `get_openapi_spec`, `get_asyncapi_spec`, `search_api_endpoints`, `search_websocket_events`, `get_endpoint_details`, `get_api_environment` |
|
|
106
|
-
| **Extended API** (
|
|
106
|
+
| **Extended API** (7) | `api_list_tags`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_find_events_for_operation`, `api_list_events`, `api_generate_dependency` |
|
|
107
107
|
| **Config editor** (13) | `config_validate`, `config_list_field_types`, `config_list_card_types`, `config_get_field_schema`, `config_list_cards`, `config_list_fields`, `config_add_field`, `config_move_field`, `config_remove_field`, `config_add_card`, `config_move_card`, `config_remove_card`, `config_extract_strings` |
|
|
108
108
|
| **Docs search** (3) | `docs_list_pages`, `docs_search`, `docs_get_page` — full-text across `docs.gxp.dev` via its sitemap |
|
|
109
109
|
| **Test helpers** (2) | `test_scaffold_component_test`, `test_api_route` |
|
|
110
|
+
| **Data models** (1) | `describe_data_models` — enumerate or detail OpenAPI `components.schemas` with $ref + allOf flattening |
|
|
111
|
+
| **UIKit** (1) | `list_uikit_components` — list components exported by `@gxp-dev/uikit` installed in the current project |
|
|
112
|
+
|
|
113
|
+
The previous bin name `gxp-api-server` still ships as a deprecation shim — it prints a stderr notice and forwards to the same server. Update your `.mcp.json` / `.gemini/settings.json` to use `mcp-serve` going forward.
|
|
110
114
|
|
|
111
115
|
Every config mutation is linter-guarded: invalid writes are refused unless `force: true`, so AI agents can't save broken state.
|
|
112
116
|
|
package/bin/lib/cli.js
CHANGED
|
@@ -383,8 +383,10 @@ const cli = yargs
|
|
|
383
383
|
.epilog(
|
|
384
384
|
[
|
|
385
385
|
"Docs: https://docs.gxp.dev",
|
|
386
|
-
"AI/MCP: the gxp-api MCP server (bin:
|
|
387
|
-
"
|
|
386
|
+
"AI/MCP: the gxp-api MCP server (bin: mcp-serve) exposes 33 tools across",
|
|
387
|
+
" API specs, config editing, docs search, test helpers, data",
|
|
388
|
+
" models, and UIKit components. (gxp-api-server still works as a",
|
|
389
|
+
" deprecation shim.)",
|
|
388
390
|
].join("\n"),
|
|
389
391
|
)
|
|
390
392
|
.help("h")
|
package/bin/lib/commands/init.js
CHANGED
|
@@ -180,8 +180,9 @@ function copyBundleFiles(projectPath, paths, overwrite = false) {
|
|
|
180
180
|
},
|
|
181
181
|
// Canonical project-scoped MCP config files. The AI CLIs each look for
|
|
182
182
|
// their own file at the project root and auto-load the `gxp-api` server
|
|
183
|
-
// from there. The `
|
|
184
|
-
// @gxp-dev/tools, so no extra install step is required.
|
|
183
|
+
// from there. The `mcp-serve` binary is installed on PATH by
|
|
184
|
+
// @gxp-dev/tools, so no extra install step is required. (The legacy
|
|
185
|
+
// `gxp-api-server` name still works as a deprecation shim.)
|
|
185
186
|
{
|
|
186
187
|
src: "mcp.json",
|
|
187
188
|
dest: ".mcp.json",
|
|
@@ -973,10 +973,10 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
|
|
|
973
973
|
|
|
974
974
|
const mcpFixCommand =
|
|
975
975
|
{
|
|
976
|
-
claude: "claude mcp add gxp-api
|
|
977
|
-
codex: "codex mcp add gxp-api
|
|
976
|
+
claude: "claude mcp add gxp-api mcp-serve",
|
|
977
|
+
codex: "codex mcp add gxp-api mcp-serve",
|
|
978
978
|
gemini:
|
|
979
|
-
"add the `gxp-api` server to `~/.gemini/settings.json` under `mcpServers` with command `
|
|
979
|
+
"add the `gxp-api` server to `~/.gemini/settings.json` under `mcpServers` with command `mcp-serve`",
|
|
980
980
|
}[provider] || "see the provider's MCP docs"
|
|
981
981
|
|
|
982
982
|
return [
|
|
@@ -986,16 +986,18 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
|
|
|
986
986
|
"",
|
|
987
987
|
`Start a new GxP plugin development session. First read \`${instructionFile}\` and \`app-instructions.md\` in this project — they describe the workflow, conventions, and the full set of tools available to you.${claudeAgentHint}`,
|
|
988
988
|
"",
|
|
989
|
-
"**Step 0 — smoke-test the `gxp-api` MCP server before asking me anything.** The server is defined in `.mcp.json` / `.gemini/settings.json` at the project root and provided by the `
|
|
989
|
+
"**Step 0 — smoke-test the `gxp-api` MCP server before asking me anything.** The server is defined in `.mcp.json` / `.gemini/settings.json` at the project root and provided by the `mcp-serve` binary that ships with `@gxp-dev/tools` (already on your PATH — verify with `which mcp-serve`). Call `api_list_tags` immediately:",
|
|
990
990
|
"- If it returns a list of tags → the MCP is live; load the tag list into memory so your first question to me can be grounded in the platform's actual resources (attendees, events, quizzes, posts, forms, etc.) rather than abstract categories.",
|
|
991
991
|
`- If \`api_list_tags\` (or any other \`api_*\` / \`config_*\` / \`docs_*\` tool) is not available, the MCP didn't register. Tell me so and suggest I run \`${mcpFixCommand}\`, then restart this session. Do not proceed without the MCP — every operationId, event, and dependency you plan with must come from it.`,
|
|
992
992
|
"",
|
|
993
|
-
"You have the `gxp-api` MCP server available with
|
|
993
|
+
"You have the `gxp-api` MCP server available with 33 tools across seven families:",
|
|
994
994
|
"- **API spec discovery** — `search_api_endpoints`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_generate_dependency`, `get_endpoint_details`, `api_list_tags`.",
|
|
995
995
|
"- **WebSocket events** — `api_find_events_for_operation` (maps an operationId to the AsyncAPI events it triggers), `api_list_events`, `search_websocket_events`.",
|
|
996
996
|
"- **Config editing** — `config_add_card`, `config_add_field`, `config_list_field_types`, `config_get_field_schema`, `config_extract_strings`, `config_validate`, etc. Every mutation is linter-guarded against the schemas in `bin/lib/lint/schemas/`.",
|
|
997
997
|
"- **Docs search** — `docs_search`, `docs_get_page`, `docs_list_pages` (full-text search across docs.gxp.dev).",
|
|
998
998
|
"- **Test helpers** — `test_scaffold_component_test`, `test_api_route`.",
|
|
999
|
+
"- **Data models** — `describe_data_models` (enumerate or detail OpenAPI components.schemas; walks allOf and resolves $ref by name).",
|
|
1000
|
+
"- **UIKit** — `list_uikit_components` (list components exported by `@gxp-dev/uikit` installed in this project).",
|
|
999
1001
|
"",
|
|
1000
1002
|
"Follow the full workflow from the instructions: (1) understand the feature, (2) discover data sources via MCP, (3) plan including the admin configuration form, (4) implement, (5) **sync the manifest and build the admin form**, (6) test with real broadcasts, (7) final `gxdev lint --all`.",
|
|
1001
1003
|
"",
|
|
@@ -1033,8 +1035,8 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
|
|
|
1033
1035
|
*/
|
|
1034
1036
|
function registerMcpWithProviderCli(provider) {
|
|
1035
1037
|
const commands = {
|
|
1036
|
-
claude: ["mcp", "add", "gxp-api", "
|
|
1037
|
-
codex: ["mcp", "add", "gxp-api", "
|
|
1038
|
+
claude: ["mcp", "add", "gxp-api", "mcp-serve"],
|
|
1039
|
+
codex: ["mcp", "add", "gxp-api", "mcp-serve"],
|
|
1038
1040
|
}
|
|
1039
1041
|
const args = commands[provider]
|
|
1040
1042
|
if (!args) {
|
|
@@ -1117,7 +1119,7 @@ function launchInteractiveAISession(
|
|
|
1117
1119
|
" The `gxp-api` MCP server is configured in .mcp.json / .gemini/settings.json",
|
|
1118
1120
|
)
|
|
1119
1121
|
console.log(
|
|
1120
|
-
" at the project root (binary:
|
|
1122
|
+
" at the project root (binary: mcp-serve, installed with @gxp-dev/tools).",
|
|
1121
1123
|
)
|
|
1122
1124
|
console.log(
|
|
1123
1125
|
" The agent will smoke-test it first, then greet you and ask what you want",
|
package/mcp/gxp-api-server.js
CHANGED
|
@@ -1,484 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Deprecated bin. Forwards to the same stdio MCP server as `mcp-serve`,
|
|
5
|
+
* but prints a one-time stderr notice on startup so existing scaffolded
|
|
6
|
+
* projects keep working while users migrate their MCP configs to the new
|
|
7
|
+
* name. This shim will be removed in a future major release.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
9
|
+
* Migration:
|
|
10
|
+
* - claude: `claude mcp add gxp-api mcp-serve`
|
|
11
|
+
* - codex: `codex mcp add gxp-api mcp-serve`
|
|
12
|
+
* - gemini: set "command": "mcp-serve" in ~/.gemini/settings.json
|
|
13
|
+
* - .mcp.json / .gemini/settings.json: swap "gxp-api-server" -> "mcp-serve"
|
|
8
14
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* - Caches specs in memory for performance
|
|
12
|
-
* - Provides tools for fetching full specs or searching endpoints
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* node mcp/gxp-api-server.js
|
|
16
|
-
*
|
|
17
|
-
* Configure in your AI tool's MCP settings to enable API-aware assistance.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const readline = require("readline")
|
|
21
|
-
const {
|
|
22
|
-
ENVIRONMENT_URLS,
|
|
23
|
-
getEnvironment,
|
|
24
|
-
getEnvUrls,
|
|
25
|
-
fetchSpec,
|
|
26
|
-
} = require("./lib/specs")
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Search OpenAPI spec for endpoints matching a query
|
|
30
|
-
*/
|
|
31
|
-
function searchEndpoints(spec, query) {
|
|
32
|
-
const results = []
|
|
33
|
-
const queryLower = query.toLowerCase()
|
|
34
|
-
|
|
35
|
-
if (spec.paths) {
|
|
36
|
-
for (const [path, methods] of Object.entries(spec.paths)) {
|
|
37
|
-
for (const [method, details] of Object.entries(methods)) {
|
|
38
|
-
if (
|
|
39
|
-
typeof details === "object" &&
|
|
40
|
-
(path.toLowerCase().includes(queryLower) ||
|
|
41
|
-
details.summary?.toLowerCase().includes(queryLower) ||
|
|
42
|
-
details.description?.toLowerCase().includes(queryLower) ||
|
|
43
|
-
details.operationId?.toLowerCase().includes(queryLower) ||
|
|
44
|
-
details.tags?.some((t) => t.toLowerCase().includes(queryLower)))
|
|
45
|
-
) {
|
|
46
|
-
results.push({
|
|
47
|
-
path,
|
|
48
|
-
method: method.toUpperCase(),
|
|
49
|
-
summary: details.summary || "",
|
|
50
|
-
description: details.description || "",
|
|
51
|
-
operationId: details.operationId || "",
|
|
52
|
-
tags: details.tags || [],
|
|
53
|
-
parameters: details.parameters || [],
|
|
54
|
-
requestBody: details.requestBody || null,
|
|
55
|
-
responses: Object.keys(details.responses || {}),
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return results
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
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.
|
|
74
|
-
*/
|
|
75
|
-
function searchEvents(spec, query) {
|
|
76
|
-
const results = []
|
|
77
|
-
const queryLower = query.toLowerCase()
|
|
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
|
-
|
|
100
|
-
if (spec.channels) {
|
|
101
|
-
for (const [channel, details] of Object.entries(spec.channels)) {
|
|
102
|
-
if (
|
|
103
|
-
channel.toLowerCase().includes(queryLower) ||
|
|
104
|
-
details.description?.toLowerCase().includes(queryLower)
|
|
105
|
-
) {
|
|
106
|
-
const operations = []
|
|
107
|
-
if (details.publish) {
|
|
108
|
-
operations.push({
|
|
109
|
-
type: "publish",
|
|
110
|
-
summary: details.publish.summary || "",
|
|
111
|
-
message: details.publish.message || null,
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
if (details.subscribe) {
|
|
115
|
-
operations.push({
|
|
116
|
-
type: "subscribe",
|
|
117
|
-
summary: details.subscribe.summary || "",
|
|
118
|
-
message: details.subscribe.message || null,
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
results.push({
|
|
123
|
-
kind: "channel",
|
|
124
|
-
channel,
|
|
125
|
-
description: details.description || "",
|
|
126
|
-
operations,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return results
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Get endpoint details by path and method
|
|
137
|
-
*/
|
|
138
|
-
function getEndpointDetails(spec, path, method) {
|
|
139
|
-
const methodLower = method.toLowerCase()
|
|
140
|
-
const endpoint = spec.paths?.[path]?.[methodLower]
|
|
141
|
-
|
|
142
|
-
if (!endpoint) {
|
|
143
|
-
return null
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
path,
|
|
148
|
-
method: method.toUpperCase(),
|
|
149
|
-
summary: endpoint.summary || "",
|
|
150
|
-
description: endpoint.description || "",
|
|
151
|
-
operationId: endpoint.operationId || "",
|
|
152
|
-
tags: endpoint.tags || [],
|
|
153
|
-
parameters: endpoint.parameters || [],
|
|
154
|
-
requestBody: endpoint.requestBody || null,
|
|
155
|
-
responses: endpoint.responses || {},
|
|
156
|
-
security: endpoint.security || spec.security || [],
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
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
|
-
|
|
185
|
-
const SERVER_INFO = {
|
|
186
|
-
name: "gxp-api-server",
|
|
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.",
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const API_TOOLS = [
|
|
193
|
-
{
|
|
194
|
-
name: "get_openapi_spec",
|
|
195
|
-
description:
|
|
196
|
-
"Fetch the full OpenAPI specification for the GxP API. Returns the complete spec including all endpoints, schemas, and documentation.",
|
|
197
|
-
inputSchema: {
|
|
198
|
-
type: "object",
|
|
199
|
-
properties: {},
|
|
200
|
-
required: [],
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
name: "get_asyncapi_spec",
|
|
205
|
-
description:
|
|
206
|
-
"Fetch the AsyncAPI specification for GxP WebSocket events. Returns channel definitions, message schemas, and event documentation.",
|
|
207
|
-
inputSchema: {
|
|
208
|
-
type: "object",
|
|
209
|
-
properties: {},
|
|
210
|
-
required: [],
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
name: "search_api_endpoints",
|
|
215
|
-
description:
|
|
216
|
-
"Search for API endpoints matching a query. Searches path, summary, description, operation ID, and tags.",
|
|
217
|
-
inputSchema: {
|
|
218
|
-
type: "object",
|
|
219
|
-
properties: {
|
|
220
|
-
query: {
|
|
221
|
-
type: "string",
|
|
222
|
-
description:
|
|
223
|
-
"Search term to find matching endpoints (e.g., 'attendee', 'check-in', 'event')",
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
required: ["query"],
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
name: "search_websocket_events",
|
|
231
|
-
description:
|
|
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).",
|
|
233
|
-
inputSchema: {
|
|
234
|
-
type: "object",
|
|
235
|
-
properties: {
|
|
236
|
-
query: {
|
|
237
|
-
type: "string",
|
|
238
|
-
description:
|
|
239
|
-
"Search term to find matching events (e.g., 'message', 'created', 'updated')",
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
required: ["query"],
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
name: "get_endpoint_details",
|
|
247
|
-
description:
|
|
248
|
-
"Get detailed information about a specific API endpoint including parameters, request body, and responses.",
|
|
249
|
-
inputSchema: {
|
|
250
|
-
type: "object",
|
|
251
|
-
properties: {
|
|
252
|
-
path: {
|
|
253
|
-
type: "string",
|
|
254
|
-
description: "API endpoint path (e.g., '/api/v1/attendees')",
|
|
255
|
-
},
|
|
256
|
-
method: {
|
|
257
|
-
type: "string",
|
|
258
|
-
description: "HTTP method (GET, POST, PUT, PATCH, DELETE)",
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
required: ["path", "method"],
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "get_api_environment",
|
|
266
|
-
description:
|
|
267
|
-
"Get the current API environment configuration including base URL and spec URLs.",
|
|
268
|
-
inputSchema: {
|
|
269
|
-
type: "object",
|
|
270
|
-
properties: {},
|
|
271
|
-
required: [],
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
]
|
|
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
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Handle MCP tool calls
|
|
288
|
-
*/
|
|
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
|
-
}
|
|
302
|
-
switch (name) {
|
|
303
|
-
case "get_openapi_spec": {
|
|
304
|
-
const spec = await fetchSpec("openapi")
|
|
305
|
-
return {
|
|
306
|
-
content: [
|
|
307
|
-
{
|
|
308
|
-
type: "text",
|
|
309
|
-
text: JSON.stringify(spec, null, 2),
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
case "get_asyncapi_spec": {
|
|
316
|
-
const spec = await fetchSpec("asyncapi")
|
|
317
|
-
return {
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: "text",
|
|
321
|
-
text: JSON.stringify(spec, null, 2),
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
case "search_api_endpoints": {
|
|
328
|
-
const spec = await fetchSpec("openapi")
|
|
329
|
-
const results = searchEndpoints(spec, args.query)
|
|
330
|
-
return {
|
|
331
|
-
content: [
|
|
332
|
-
{
|
|
333
|
-
type: "text",
|
|
334
|
-
text:
|
|
335
|
-
results.length > 0
|
|
336
|
-
? JSON.stringify(results, null, 2)
|
|
337
|
-
: `No endpoints found matching "${args.query}"`,
|
|
338
|
-
},
|
|
339
|
-
],
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
case "search_websocket_events": {
|
|
344
|
-
const spec = await fetchSpec("asyncapi")
|
|
345
|
-
const results = searchEvents(spec, args.query)
|
|
346
|
-
return {
|
|
347
|
-
content: [
|
|
348
|
-
{
|
|
349
|
-
type: "text",
|
|
350
|
-
text:
|
|
351
|
-
results.length > 0
|
|
352
|
-
? JSON.stringify(results, null, 2)
|
|
353
|
-
: `No events found matching "${args.query}"`,
|
|
354
|
-
},
|
|
355
|
-
],
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
case "get_endpoint_details": {
|
|
360
|
-
const spec = await fetchSpec("openapi")
|
|
361
|
-
const details = getEndpointDetails(spec, args.path, args.method)
|
|
362
|
-
return {
|
|
363
|
-
content: [
|
|
364
|
-
{
|
|
365
|
-
type: "text",
|
|
366
|
-
text: details
|
|
367
|
-
? JSON.stringify(details, null, 2)
|
|
368
|
-
: `Endpoint not found: ${args.method} ${args.path}`,
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
case "get_api_environment": {
|
|
375
|
-
const env = getEnvironment()
|
|
376
|
-
const urls = getEnvUrls()
|
|
377
|
-
return {
|
|
378
|
-
content: [
|
|
379
|
-
{
|
|
380
|
-
type: "text",
|
|
381
|
-
text: JSON.stringify(
|
|
382
|
-
{
|
|
383
|
-
environment: env,
|
|
384
|
-
...urls,
|
|
385
|
-
},
|
|
386
|
-
null,
|
|
387
|
-
2,
|
|
388
|
-
),
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
default:
|
|
395
|
-
throw new Error(`Unknown tool: ${name}`)
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Process MCP JSON-RPC request
|
|
15
|
+
* The notice is written to stderr (not stdout) so it doesn't corrupt the
|
|
16
|
+
* MCP JSON-RPC stream the client is reading.
|
|
401
17
|
*/
|
|
402
|
-
async function processRequest(request) {
|
|
403
|
-
const { method, params, id } = request
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
let result
|
|
407
|
-
|
|
408
|
-
switch (method) {
|
|
409
|
-
case "initialize":
|
|
410
|
-
result = {
|
|
411
|
-
protocolVersion: "2024-11-05",
|
|
412
|
-
capabilities: {
|
|
413
|
-
tools: {},
|
|
414
|
-
},
|
|
415
|
-
serverInfo: SERVER_INFO,
|
|
416
|
-
}
|
|
417
|
-
break
|
|
418
|
-
|
|
419
|
-
case "tools/list":
|
|
420
|
-
result = { tools: TOOLS }
|
|
421
|
-
break
|
|
422
|
-
|
|
423
|
-
case "tools/call":
|
|
424
|
-
result = await handleToolCall(params.name, params.arguments || {})
|
|
425
|
-
break
|
|
426
|
-
|
|
427
|
-
case "notifications/initialized":
|
|
428
|
-
// No response needed for notifications
|
|
429
|
-
return null
|
|
430
|
-
|
|
431
|
-
default:
|
|
432
|
-
throw new Error(`Unknown method: ${method}`)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return { jsonrpc: "2.0", id, result }
|
|
436
|
-
} catch (error) {
|
|
437
|
-
return {
|
|
438
|
-
jsonrpc: "2.0",
|
|
439
|
-
id,
|
|
440
|
-
error: {
|
|
441
|
-
code: -32603,
|
|
442
|
-
message: error.message,
|
|
443
|
-
},
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Main server loop
|
|
450
|
-
*/
|
|
451
|
-
async function main() {
|
|
452
|
-
const rl = readline.createInterface({
|
|
453
|
-
input: process.stdin,
|
|
454
|
-
output: process.stdout,
|
|
455
|
-
terminal: false,
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
for await (const line of rl) {
|
|
459
|
-
if (!line.trim()) continue
|
|
460
18
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
19
|
+
process.stderr.write(
|
|
20
|
+
"[gxp-api-server] DEPRECATED: this bin name will be removed in a future major release. Use `mcp-serve` instead — update your .mcp.json / .gemini/settings.json or rerun `claude mcp add gxp-api mcp-serve`.\n",
|
|
21
|
+
)
|
|
464
22
|
|
|
465
|
-
|
|
466
|
-
console.log(JSON.stringify(response))
|
|
467
|
-
}
|
|
468
|
-
} catch (error) {
|
|
469
|
-
console.log(
|
|
470
|
-
JSON.stringify({
|
|
471
|
-
jsonrpc: "2.0",
|
|
472
|
-
id: null,
|
|
473
|
-
error: {
|
|
474
|
-
code: -32700,
|
|
475
|
-
message: `Parse error: ${error.message}`,
|
|
476
|
-
},
|
|
477
|
-
}),
|
|
478
|
-
)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
23
|
+
const { startServer } = require("./lib/server")
|
|
482
24
|
|
|
483
|
-
|
|
484
|
-
|
|
25
|
+
startServer().catch((err) => {
|
|
26
|
+
console.error(err && err.stack ? err.stack : err)
|
|
27
|
+
process.exit(1)
|
|
28
|
+
})
|