@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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for @gxp-dev/uikit introspection.
|
|
3
|
+
*
|
|
4
|
+
* - list_uikit_components : enumerate components exported by the version
|
|
5
|
+
* of @gxp-dev/uikit installed in the *plugin project's* node_modules
|
|
6
|
+
* (not the toolkit's). Resolves the package relative to process.cwd(),
|
|
7
|
+
* parses named exports out of dist/index.d.ts, and returns sorted
|
|
8
|
+
* PascalCase names plus the package version.
|
|
9
|
+
*
|
|
10
|
+
* We parse the .d.ts with a regex pair (no TS AST) because the file is the
|
|
11
|
+
* built output and is shaped by the package's own build, not by us. Edge
|
|
12
|
+
* cases that the regex misses (e.g. nested namespace exports) are
|
|
13
|
+
* acceptable: the agent gets a strong starting set and can fall back to
|
|
14
|
+
* docs_search for anything unusual.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require("fs")
|
|
18
|
+
const path = require("path")
|
|
19
|
+
|
|
20
|
+
function contentResult(obj) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve @gxp-dev/uikit from the given cwd. Returns { root, pkg } where
|
|
28
|
+
* root is the absolute path to the uikit package directory and pkg is the
|
|
29
|
+
* parsed package.json, or null if uikit is not installed at any level.
|
|
30
|
+
*
|
|
31
|
+
* We mimic Node's node_modules walk-up rather than calling
|
|
32
|
+
* require.resolve("@gxp-dev/uikit/...") because the uikit's package.json
|
|
33
|
+
* declares a strict `exports` field with only `import` and `types`
|
|
34
|
+
* conditions. From a CJS context, require.resolve fails against that
|
|
35
|
+
* exports map even though the directory exists on disk. Walking the
|
|
36
|
+
* filesystem directly sidesteps it and works under npm, pnpm (where
|
|
37
|
+
* @gxp-dev/uikit is a symlink into .pnpm), and yarn workspaces.
|
|
38
|
+
*/
|
|
39
|
+
function resolveUikit(cwd = process.cwd()) {
|
|
40
|
+
let dir = path.resolve(cwd)
|
|
41
|
+
while (dir) {
|
|
42
|
+
const candidate = path.join(dir, "node_modules", "@gxp-dev", "uikit")
|
|
43
|
+
const pkgPath = path.join(candidate, "package.json")
|
|
44
|
+
if (fs.existsSync(pkgPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
|
47
|
+
if (pkg && pkg.name === "@gxp-dev/uikit") {
|
|
48
|
+
return { root: candidate, pkg }
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// malformed package.json — keep walking
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const parent = path.dirname(dir)
|
|
55
|
+
if (parent === dir) break
|
|
56
|
+
dir = parent
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pull named exports out of a .d.ts source string. Picks up:
|
|
63
|
+
* - export declare const/let/var Foo
|
|
64
|
+
* - export declare function Foo
|
|
65
|
+
* - export declare class Foo
|
|
66
|
+
* - export declare interface Foo
|
|
67
|
+
* - export declare type Foo
|
|
68
|
+
* - export declare enum Foo
|
|
69
|
+
* - export { Foo, Bar as Baz }
|
|
70
|
+
*/
|
|
71
|
+
function parseNamedExports(source) {
|
|
72
|
+
const names = new Set()
|
|
73
|
+
|
|
74
|
+
const declRe =
|
|
75
|
+
/export\s+(?:declare\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g
|
|
76
|
+
let m
|
|
77
|
+
while ((m = declRe.exec(source)) !== null) {
|
|
78
|
+
names.add(m[1])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const braceRe = /export\s*\{([^}]+)\}/g
|
|
82
|
+
while ((m = braceRe.exec(source)) !== null) {
|
|
83
|
+
const inner = m[1]
|
|
84
|
+
for (const rawPart of inner.split(",")) {
|
|
85
|
+
const part = rawPart.trim()
|
|
86
|
+
if (!part) continue
|
|
87
|
+
const aliasMatch = part.match(
|
|
88
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)/,
|
|
89
|
+
)
|
|
90
|
+
if (aliasMatch) {
|
|
91
|
+
names.add(aliasMatch[1])
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
const nameMatch = part.match(/^([A-Za-z_$][A-Za-z0-9_$]*)/)
|
|
95
|
+
if (nameMatch) names.add(nameMatch[1])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Array.from(names)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function listUikitComponents({ filter, cwd } = {}) {
|
|
103
|
+
const resolved = resolveUikit(cwd || process.cwd())
|
|
104
|
+
if (!resolved) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
error:
|
|
108
|
+
"Could not resolve @gxp-dev/uikit from the current project. Install it with `npm install @gxp-dev/uikit` and re-run mcp-serve from the plugin project root.",
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const { root, pkg } = resolved
|
|
112
|
+
const dtsPath = path.join(root, "dist", "index.d.ts")
|
|
113
|
+
if (!fs.existsSync(dtsPath)) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `@gxp-dev/uikit is installed at ${root} but dist/index.d.ts is missing. The package may not have been built.`,
|
|
117
|
+
resolved: root,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const src = fs.readFileSync(dtsPath, "utf-8")
|
|
122
|
+
const all = parseNamedExports(src)
|
|
123
|
+
const pascal = all.filter((n) => /^[A-Z]/.test(n)).sort()
|
|
124
|
+
|
|
125
|
+
let filtered = pascal
|
|
126
|
+
if (filter) {
|
|
127
|
+
const f = String(filter).toLowerCase()
|
|
128
|
+
filtered = pascal.filter((n) => n.toLowerCase().includes(f))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
package: {
|
|
134
|
+
name: pkg.name,
|
|
135
|
+
version: pkg.version || null,
|
|
136
|
+
root,
|
|
137
|
+
},
|
|
138
|
+
count: filtered.length,
|
|
139
|
+
components: filtered,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const UIKIT_TOOLS = [
|
|
144
|
+
{
|
|
145
|
+
name: "list_uikit_components",
|
|
146
|
+
description:
|
|
147
|
+
"Enumerate components exported by the @gxp-dev/uikit package installed in the current plugin project. Reads named exports from the built dist/index.d.ts and returns the PascalCase names plus the package version. Resolves uikit relative to the project (process.cwd()), not the toolkit, so the agent sees exactly what the project can import. Pass `filter` for a case-insensitive substring match.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
filter: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description:
|
|
154
|
+
"Case-insensitive substring filter applied to component names.",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
async function handleUikitToolCall(name, args = {}) {
|
|
162
|
+
switch (name) {
|
|
163
|
+
case "list_uikit_components":
|
|
164
|
+
return contentResult(listUikitComponents(args))
|
|
165
|
+
default:
|
|
166
|
+
throw new Error(`Unknown uikit tool: ${name}`)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isUikitTool(name) {
|
|
171
|
+
return UIKIT_TOOLS.some((t) => t.name === name)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = {
|
|
175
|
+
UIKIT_TOOLS,
|
|
176
|
+
handleUikitToolCall,
|
|
177
|
+
isUikitTool,
|
|
178
|
+
listUikitComponents,
|
|
179
|
+
parseNamedExports,
|
|
180
|
+
resolveUikit,
|
|
181
|
+
}
|
package/mcp/mcp-serve.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GxP MCP Server (stdio).
|
|
5
|
+
*
|
|
6
|
+
* Primary bin: `mcp-serve`.
|
|
7
|
+
*
|
|
8
|
+
* Speaks Model Context Protocol over stdin/stdout using the official
|
|
9
|
+
* @modelcontextprotocol/sdk's StdioServerTransport. The full tool surface
|
|
10
|
+
* and wiring live in ./lib/server.js so this file stays a thin entry point.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* mcp-serve
|
|
14
|
+
* node mcp/mcp-serve.js
|
|
15
|
+
*
|
|
16
|
+
* Configure in your AI tool's MCP settings to enable API-aware,
|
|
17
|
+
* schema-aware, test-aware assistance inside plugin projects.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { startServer } = require("./lib/server")
|
|
21
|
+
|
|
22
|
+
startServer().catch((err) => {
|
|
23
|
+
console.error(err && err.stack ? err.stack : err)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gxp-dev/tools",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.83",
|
|
4
4
|
"description": "Dev tools to create platform plugins",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"bin": {
|
|
41
41
|
"gxdev": "bin/gx-devtools.js",
|
|
42
|
+
"mcp-serve": "mcp/mcp-serve.js",
|
|
42
43
|
"gxp-api-server": "mcp/gxp-api-server.js"
|
|
43
44
|
},
|
|
44
45
|
"keywords": [
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@faker-js/faker": "^9.9.0",
|
|
59
60
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
|
61
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
60
62
|
"@vitejs/plugin-vue": "^6.0.6",
|
|
61
63
|
"adm-zip": "^0.5.17",
|
|
62
64
|
"ajv": "^8.18.0",
|
package/runtime/vite.config.js
CHANGED
|
@@ -28,7 +28,7 @@ Ask clarifying questions. Don't guess — a single clarification prevents a larg
|
|
|
28
28
|
|
|
29
29
|
The `gxp-api` MCP server is your source of truth for the platform. Never invent endpoints or event names.
|
|
30
30
|
|
|
31
|
-
**Before anything else, confirm the MCP is live** — call `api_list_tags`. The server is configured in `.mcp.json` at the project root and provided by the `
|
|
31
|
+
**Before anything else, confirm the MCP is live** — call `api_list_tags`. The server is configured in `.mcp.json` at the project root and provided by the `mcp-serve` binary that ships with `@gxp-dev/tools` (on PATH; verify with `which mcp-serve`). If the `api_*` / `config_*` / `docs_*` tools aren't available, tell the user to run `claude mcp add gxp-api mcp-serve` and restart the session. Do not proceed without it.
|
|
32
32
|
|
|
33
33
|
**API discovery:**
|
|
34
34
|
|
package/template/AGENTS.md
CHANGED
|
@@ -21,7 +21,7 @@ Ask clarifying questions instead of guessing. A 30-second question beats a 30-mi
|
|
|
21
21
|
|
|
22
22
|
Use the `gxp-api` MCP server to ground the implementation in the real platform — never invent endpoints or event names.
|
|
23
23
|
|
|
24
|
-
**Verify the MCP is live before planning anything** by calling `api_list_tags`. The server is defined in `.mcp.json` at the project root and provided by the `
|
|
24
|
+
**Verify the MCP is live before planning anything** by calling `api_list_tags`. The server is defined in `.mcp.json` at the project root and provided by the `mcp-serve` binary that ships with `@gxp-dev/tools` (on PATH — check with `which mcp-serve`). If the `api_*` / `config_*` / `docs_*` tools aren't available, tell the user to run `claude mcp add gxp-api mcp-serve` (or `codex mcp add …` / add it to `~/.gemini/settings.json`) and restart the session. Do not proceed without the MCP.
|
|
25
25
|
|
|
26
26
|
- **Find endpoints** — `api_list_tags`, `api_list_operation_ids` (optionally scoped by tag), `search_api_endpoints` (keyword).
|
|
27
27
|
- **Inspect a specific endpoint** — `api_get_operation_parameters`, `get_endpoint_details`.
|
package/template/GEMINI.md
CHANGED
|
@@ -18,7 +18,7 @@ Before writing anything, clarify with the client:
|
|
|
18
18
|
|
|
19
19
|
Ground the implementation in real platform endpoints and events. Do not invent API paths or event names.
|
|
20
20
|
|
|
21
|
-
**Verify the MCP is live first** — call `api_list_tags`. The server is defined in `.gemini/settings.json` at the project root and provided by the `
|
|
21
|
+
**Verify the MCP is live first** — call `api_list_tags`. The server is defined in `.gemini/settings.json` at the project root and provided by the `mcp-serve` binary that ships with `@gxp-dev/tools` (on PATH). If the tools aren't available, tell the user to add `gxp-api` to `~/.gemini/settings.json` under `mcpServers` with command `mcp-serve`, then restart the session.
|
|
22
22
|
|
|
23
23
|
- Endpoints — `api_list_tags`, `api_list_operation_ids`, `search_api_endpoints`, `api_get_operation_parameters`, `get_endpoint_details`.
|
|
24
24
|
- Find by payload shape — `api_find_endpoints_by_schema`.
|