@creately/rdm-mcp 0.2.0 → 0.3.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/package.json +3 -6
- package/src/cli.js +23 -0
- package/src/index.ts +1 -1
- package/src/tools/rdm_schema.ts +9 -7
- package/src/tools/rdm_validate.ts +30 -7
- package/src/tools/rdm_write.ts +15 -8
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creately/rdm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for RDM — parse, validate, render, and manipulate diagrams via AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"rdm-mcp": "src/
|
|
8
|
+
"rdm-mcp": "src/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "bun run src/index.ts",
|
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
16
|
-
"@creately/rdm-core": ">=0.
|
|
17
|
-
},
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
16
|
+
"@creately/rdm-core": ">=0.4.0"
|
|
20
17
|
},
|
|
21
18
|
"license": "MIT",
|
|
22
19
|
"repository": {
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* rdm-mcp CLI wrapper — delegates to bun for TypeScript execution.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const impl = join(__dirname, 'index.ts');
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
execFileSync('bun', ['run', impl, ...process.argv.slice(2)], {
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (e.status) process.exit(e.status);
|
|
21
|
+
console.error('rdm-mcp requires bun. Install it: curl -fsSL https://bun.sh/install | bash');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ const TOOL_EXECUTORS: Record<string, (args: Record<string, unknown>) => Promise<
|
|
|
62
62
|
|
|
63
63
|
async function main() {
|
|
64
64
|
const server = new Server(
|
|
65
|
-
{ name: 'rdm-mcp', version: '0.
|
|
65
|
+
{ name: 'rdm-mcp', version: '0.3.0' },
|
|
66
66
|
{ capabilities: { tools: {} } }
|
|
67
67
|
);
|
|
68
68
|
|
package/src/tools/rdm_schema.ts
CHANGED
|
@@ -104,20 +104,22 @@ const SCHEMAS: Record<string, DomainSchema> = {
|
|
|
104
104
|
},
|
|
105
105
|
flowchart: {
|
|
106
106
|
domain: 'flowchart',
|
|
107
|
-
description: 'Simple
|
|
107
|
+
description: 'Simple flowcharts. NOTE: flowcharts use the same syntax as process diagrams. Use frontmatter type: process and block keyword: process. There is NO flowchart block keyword.',
|
|
108
108
|
nodeTypes: {
|
|
109
|
-
|
|
109
|
+
node: {
|
|
110
110
|
fields: [
|
|
111
|
-
{ name: 'type', type: 'enum', values: ['
|
|
111
|
+
{ name: 'type', type: 'enum', values: ['task', 'gateway', 'start-event', 'end-event'], required: true, description: 'Node role in the flow' },
|
|
112
|
+
{ name: 'label', type: 'string', description: 'Display label' },
|
|
113
|
+
{ name: 'assignee', type: 'string', description: 'Role or person responsible' },
|
|
112
114
|
],
|
|
113
115
|
},
|
|
114
116
|
},
|
|
115
117
|
relationships: [
|
|
116
|
-
{ syntax: '
|
|
117
|
-
{ syntax: '
|
|
118
|
+
{ syntax: 'source -> target', description: 'Sequential flow' },
|
|
119
|
+
{ syntax: 'source -> target { label: "Yes" }', description: 'Labeled flow (for gateways)' },
|
|
118
120
|
],
|
|
119
|
-
structure: '
|
|
120
|
-
example: '---\ntype:
|
|
121
|
+
structure: 'process Name {\n begin { type: "start-event", label: "Start" }\n step1 { type: "task", label: "Do Something" }\n done { type: "end-event", label: "End" }\n begin -> step1\n step1 -> done\n}',
|
|
122
|
+
example: '---\ntype: process\ntitle: "Simple Flow"\n---\n\nprocess SimpleFlow {\n begin { type: "start-event", label: "Start" }\n submit { type: "task", label: "Submit", assignee: "User" }\n check { type: "exclusive-gateway", label: "OK?" }\n done { type: "end-event", label: "End" }\n\n begin -> submit\n submit -> check\n check -> done { label: "Yes" }\n}',
|
|
121
123
|
},
|
|
122
124
|
table: {
|
|
123
125
|
domain: 'table',
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* rdm_validate — Validate RDM text
|
|
2
|
+
* rdm_validate — Validate RDM text with auto-fix.
|
|
3
|
+
*
|
|
4
|
+
* Auto-corrects common agent mistakes (wrong block keywords, invalid syntax)
|
|
5
|
+
* before parsing. Returns both the validation result and the corrected text.
|
|
3
6
|
*/
|
|
4
|
-
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
5
8
|
import { resolve } from 'path';
|
|
6
9
|
import { RdmService } from '@creately/rdm-core';
|
|
7
10
|
|
|
@@ -10,8 +13,9 @@ const rdmService = new RdmService();
|
|
|
10
13
|
export const rdmValidateTool = {
|
|
11
14
|
name: 'rdm_validate',
|
|
12
15
|
description:
|
|
13
|
-
'Validate an .rdm file or RDM text.
|
|
14
|
-
'
|
|
16
|
+
'Validate an .rdm file or RDM text. Auto-fixes common mistakes (wrong block keywords, ' +
|
|
17
|
+
'invalid syntax) and returns the corrected text along with any remaining errors. ' +
|
|
18
|
+
'If autoFix is true (default), writes the fixed text back to the file.',
|
|
15
19
|
inputSchema: {
|
|
16
20
|
type: 'object' as const,
|
|
17
21
|
properties: {
|
|
@@ -23,17 +27,22 @@ export const rdmValidateTool = {
|
|
|
23
27
|
type: 'string',
|
|
24
28
|
description: 'RDM text to validate directly (alternative to path)',
|
|
25
29
|
},
|
|
30
|
+
autoFix: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
description: 'If true (default), auto-fix mistakes and write corrected file back. Set to false for dry-run.',
|
|
33
|
+
},
|
|
26
34
|
},
|
|
27
35
|
},
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
export async function executeRdmValidate(args: Record<string, unknown>): Promise<unknown> {
|
|
31
39
|
let rdmText: string;
|
|
40
|
+
const filePath = args.path ? resolve(String(args.path)) : null;
|
|
41
|
+
const autoFix = args.autoFix !== false; // default true
|
|
32
42
|
|
|
33
43
|
if (args.rdmText) {
|
|
34
44
|
rdmText = String(args.rdmText);
|
|
35
|
-
} else if (
|
|
36
|
-
const filePath = resolve(String(args.path));
|
|
45
|
+
} else if (filePath) {
|
|
37
46
|
if (!existsSync(filePath)) {
|
|
38
47
|
return { error: `File not found: ${filePath}` };
|
|
39
48
|
}
|
|
@@ -42,10 +51,24 @@ export async function executeRdmValidate(args: Record<string, unknown>): Promise
|
|
|
42
51
|
return { error: 'Either path or rdmText must be provided' };
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
const result = rdmService.
|
|
54
|
+
const result = rdmService.parseAndFix(rdmText);
|
|
55
|
+
|
|
56
|
+
// Auto-fix: write corrected text back to file
|
|
57
|
+
if (autoFix && result.wasFixed && filePath) {
|
|
58
|
+
writeFileSync(filePath, result.fixedText, 'utf-8');
|
|
59
|
+
}
|
|
46
60
|
|
|
47
61
|
return {
|
|
48
62
|
valid: result.success,
|
|
63
|
+
wasFixed: result.wasFixed,
|
|
64
|
+
fixedText: result.wasFixed ? result.fixedText : undefined,
|
|
65
|
+
fixes: result.fixes.map((f) => ({
|
|
66
|
+
line: f.line,
|
|
67
|
+
rule: f.rule,
|
|
68
|
+
description: f.description,
|
|
69
|
+
before: f.before,
|
|
70
|
+
after: f.after,
|
|
71
|
+
})),
|
|
49
72
|
errors: result.errors.map((e) => ({
|
|
50
73
|
code: e.code,
|
|
51
74
|
message: e.message,
|
package/src/tools/rdm_write.ts
CHANGED
|
@@ -68,32 +68,39 @@ export async function executeRdmWrite(args: Record<string, unknown>): Promise<un
|
|
|
68
68
|
rdmText = existing.slice(0, lastBrace) + '\n ' + fragment!.trim() + '\n' + existing.slice(lastBrace);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
71
|
+
// Auto-lint and validate before writing
|
|
72
|
+
const result = rdmService.parseAndFix(rdmText);
|
|
73
|
+
if (!result.success) {
|
|
74
74
|
return {
|
|
75
75
|
error: 'Validation failed — not written',
|
|
76
|
-
errors:
|
|
76
|
+
errors: result.errors,
|
|
77
|
+
fixes: result.fixes,
|
|
78
|
+
fixedText: result.wasFixed ? result.fixedText : undefined,
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
// Use the fixed text
|
|
83
|
+
const finalText = result.fixedText;
|
|
84
|
+
|
|
80
85
|
// Ensure directory exists
|
|
81
86
|
const dir = dirname(filePath);
|
|
82
87
|
if (!existsSync(dir)) {
|
|
83
88
|
mkdirSync(dir, { recursive: true });
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
writeFileSync(filePath,
|
|
91
|
+
writeFileSync(filePath, finalText, 'utf-8');
|
|
87
92
|
|
|
88
|
-
const nodeCount = countType(
|
|
89
|
-
const edgeCount = countType(
|
|
93
|
+
const nodeCount = countType(result.document!.structure.declarations, 'node');
|
|
94
|
+
const edgeCount = countType(result.document!.structure.declarations, 'edge');
|
|
90
95
|
|
|
91
96
|
return {
|
|
92
97
|
path: filePath,
|
|
93
98
|
written: true,
|
|
99
|
+
wasFixed: result.wasFixed,
|
|
100
|
+
fixes: result.fixes,
|
|
94
101
|
nodeCount,
|
|
95
102
|
edgeCount,
|
|
96
|
-
sizeBytes: Buffer.byteLength(
|
|
103
|
+
sizeBytes: Buffer.byteLength(finalText, 'utf-8'),
|
|
97
104
|
};
|
|
98
105
|
}
|
|
99
106
|
|