@mrfentmen/openedge-abl-mcp 1.0.0 → 1.0.1

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.
@@ -0,0 +1,2 @@
1
+ import type { FormatModule } from "./types.js";
2
+ export declare const format: FormatModule;
package/dist/format.js ADDED
@@ -0,0 +1,105 @@
1
+ function stripComments(text) {
2
+ return text.replace(/\/\*[\s\S]*?\*\//g, "");
3
+ }
4
+ function parse(text) {
5
+ const issues = [];
6
+ const records = [];
7
+ const source = stripComments(text);
8
+ const variables = new Set();
9
+ const tables = new Set();
10
+ let openers = 0;
11
+ let closers = 0;
12
+ source.split(/\r?\n/).forEach((raw, index) => {
13
+ const number = index + 1;
14
+ const line = raw.replace(/\/\/.*$/, "").replace(/^\s*\*.*$/, "").trim();
15
+ if (line.length === 0)
16
+ return;
17
+ const variable = /^DEFINE\s+(?:NEW\s+)?(?:SHARED\s+)?(?:GLOBAL\s+)?VARIABLE\s+([\w-]+)(?:\s+AS\s+([\w.-]+))?/i.exec(line);
18
+ if (variable) {
19
+ const name = variable[1];
20
+ if (!variable[2]) {
21
+ issues.push({ code: "variable-without-type", severity: "error", message: `DEFINE VARIABLE ${name} has no AS type`, line: number });
22
+ }
23
+ if (variables.has(name)) {
24
+ issues.push({ code: "duplicate-variable", severity: "error", message: `Variable ${name} is defined twice`, line: number });
25
+ }
26
+ variables.add(name);
27
+ records.push({ id: `variable:${name}`, kind: "variable", fields: { name, type: variable[2] ?? "", line: number } });
28
+ return;
29
+ }
30
+ const procedure = /^(?:PROCEDURE|FUNCTION|METHOD)\s+([\w-]+)\s*:/i.exec(line);
31
+ if (procedure) {
32
+ openers += 1;
33
+ records.push({ id: `procedure:${procedure[1]}`, kind: "procedure", fields: { name: procedure[1], line: number } });
34
+ return;
35
+ }
36
+ if (/^END\s+PROCEDURE\.?\s*$/i.test(line) || /^END\s+FUNCTION\.?\s*$/i.test(line) || /^END\s+METHOD\.?\s*$/i.test(line)) {
37
+ closers += 1;
38
+ return;
39
+ }
40
+ if (/^END\.?\s*$/i.test(line)) {
41
+ closers += 1;
42
+ return;
43
+ }
44
+ const block = /^(FOR\s+EACH|FOR\s+FIRST|REPEAT|DO|CASE)\b/i.exec(line);
45
+ if (block) {
46
+ openers += 1;
47
+ records.push({ id: `block:${number}`, kind: "block", fields: { kind: block[1].toUpperCase(), line: number } });
48
+ return;
49
+ }
50
+ const table = /^(?:FOR\s+EACH|FOR\s+FIRST)\s+([\w.]+)/i.exec(line);
51
+ if (table) {
52
+ tables.add(table[1]);
53
+ }
54
+ if (!line.endsWith(".") && !line.endsWith(":") && !line.endsWith("{")) {
55
+ issues.push({ code: "missing-period", severity: "warning", message: `Statement does not end with a period: ${line.slice(0, 40)}`, line: number });
56
+ }
57
+ records.push({ id: `stmt:${number}`, kind: "statement", fields: { text: line.slice(0, 120), line: number } });
58
+ });
59
+ if (openers !== closers) {
60
+ issues.push({ code: "unbalanced-blocks", severity: "error", message: `${openers} block opener(s) but ${closers} END statement(s)` });
61
+ }
62
+ return {
63
+ format: "OpenEdge ABL",
64
+ records,
65
+ meta: {
66
+ variables: variables.size,
67
+ procedures: records.filter((record) => record.kind === "procedure").length,
68
+ blocks: records.filter((record) => record.kind === "block").length,
69
+ statements: records.filter((record) => record.kind === "statement").length,
70
+ },
71
+ issues,
72
+ };
73
+ }
74
+ const reference = [
75
+ "OpenEdge ABL source",
76
+ "",
77
+ "DEFINE VARIABLE cName AS CHARACTER NO-UNDO.",
78
+ "",
79
+ "PROCEDURE LoadData:",
80
+ " FOR EACH Customer NO-LOCK:",
81
+ " DISPLAY Customer.Name.",
82
+ " END.",
83
+ "END PROCEDURE.",
84
+ "",
85
+ "Statements end with a period.",
86
+ "PROCEDURE, FUNCTION, FOR EACH and DO open a block closed by END.",
87
+ "/* */ and // are comments.",
88
+ ].join("\n");
89
+ export const format = {
90
+ slug: "openedge-abl",
91
+ displayName: "OpenEdge ABL",
92
+ extensions: [".p", ".cls", ".i"],
93
+ description: "Parse OpenEdge ABL source, validate variables, blocks and periods.",
94
+ reference,
95
+ parse,
96
+ sample: () => [
97
+ "DEFINE VARIABLE cName AS CHARACTER NO-UNDO.",
98
+ "",
99
+ "PROCEDURE LoadData:",
100
+ " FOR EACH Customer NO-LOCK:",
101
+ " DISPLAY Customer.Name.",
102
+ " END.",
103
+ "END PROCEDURE.",
104
+ ].join("\n") + "\n",
105
+ };
package/dist/index.js CHANGED
@@ -5,9 +5,9 @@ async function main() {
5
5
  const server = createServer();
6
6
  const transport = new StdioServerTransport();
7
7
  await server.connect(transport);
8
- console.error(`${process.env.npm_package_name ?? "language-mcp"} running on stdio`);
9
8
  }
10
9
  main().catch((error) => {
11
- console.error("Fatal error:", error);
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ process.stderr.write(`${message}\n`);
12
12
  process.exit(1);
13
13
  });
package/dist/run.d.ts CHANGED
@@ -1,28 +1,17 @@
1
1
  export declare class ToolError extends Error {
2
+ constructor(message: string);
2
3
  }
3
- export interface RunResult {
4
- stdout: string;
5
- stderr: string;
6
- code: number;
7
- }
8
- export interface RunOptions {
9
- input?: string;
10
- timeoutMs?: number;
11
- cwd?: string;
12
- }
13
- /** Run a command without a shell and capture both streams. */
14
- export declare function run(command: string, args: string[], options?: RunOptions): Promise<RunResult>;
4
+ export declare function errorMessage(error: unknown): string;
15
5
  export declare function text(value: string): {
16
6
  content: {
17
7
  type: "text";
18
8
  text: string;
19
9
  }[];
20
10
  };
21
- export declare function fail(value: string): {
11
+ export declare function fail(message: string): {
12
+ isError: boolean;
22
13
  content: {
23
14
  type: "text";
24
15
  text: string;
25
16
  }[];
26
- isError: boolean;
27
17
  };
28
- export declare function errorMessage(error: unknown): string;
package/dist/run.js CHANGED
@@ -1,56 +1,15 @@
1
- import { spawn } from "node:child_process";
2
1
  export class ToolError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "ToolError";
5
+ }
3
6
  }
4
- /** Run a command without a shell and capture both streams. */
5
- export function run(command, args, options = {}) {
6
- return new Promise((resolve, reject) => {
7
- const child = spawn(command, args, {
8
- cwd: options.cwd,
9
- stdio: ["pipe", "pipe", "pipe"],
10
- });
11
- let stdout = "";
12
- let stderr = "";
13
- let settled = false;
14
- const timeoutMs = options.timeoutMs ?? 30_000;
15
- const timer = setTimeout(() => {
16
- if (settled)
17
- return;
18
- settled = true;
19
- child.kill("SIGKILL");
20
- reject(new ToolError(`${command} timed out after ${timeoutMs}ms`));
21
- }, timeoutMs);
22
- child.on("error", (error) => {
23
- if (settled)
24
- return;
25
- settled = true;
26
- clearTimeout(timer);
27
- reject(new ToolError(`${command} could not start: ${error.message}`));
28
- });
29
- child.stdout.on("data", (chunk) => {
30
- stdout += chunk.toString();
31
- });
32
- child.stderr.on("data", (chunk) => {
33
- stderr += chunk.toString();
34
- });
35
- child.stdin.on("error", () => { });
36
- child.on("close", (code) => {
37
- if (settled)
38
- return;
39
- settled = true;
40
- clearTimeout(timer);
41
- resolve({ stdout, stderr, code: code ?? 0 });
42
- });
43
- if (options.input !== undefined)
44
- child.stdin.write(options.input);
45
- child.stdin.end();
46
- });
7
+ export function errorMessage(error) {
8
+ return error instanceof Error ? error.message : String(error);
47
9
  }
48
10
  export function text(value) {
49
11
  return { content: [{ type: "text", text: value }] };
50
12
  }
51
- export function fail(value) {
52
- return { content: [{ type: "text", text: value }], isError: true };
53
- }
54
- export function errorMessage(error) {
55
- return error instanceof Error ? error.message : String(error);
13
+ export function fail(message) {
14
+ return { isError: true, content: [{ type: "text", text: message }] };
56
15
  }
package/dist/server.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const VERSION = "1.0.0";
2
3
  export declare function createServer(): McpServer;
package/dist/server.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { config } from "./config.js";
3
- import { registerLanguageTools } from "./core.js";
2
+ import { format } from "./format.js";
3
+ import { registerFormatTools } from "./core.js";
4
+ export const VERSION = "1.0.0";
4
5
  export function createServer() {
5
- const server = new McpServer({ name: `${config.slug}-mcp`, version: config.version });
6
- registerLanguageTools(server, config);
6
+ const server = new McpServer({ name: `${format.slug}-mcp`, version: VERSION });
7
+ registerFormatTools(server, format);
7
8
  return server;
8
9
  }
package/dist/types.d.ts CHANGED
@@ -1,47 +1,31 @@
1
- export interface CommandSpec {
2
- command: string;
3
- args: string[];
4
- timeoutMs?: number;
1
+ export type Severity = "error" | "warning";
2
+ export interface Issue {
3
+ code: string;
4
+ severity: Severity;
5
+ message: string;
6
+ line?: number;
5
7
  }
6
- export interface SymbolRule {
8
+ export interface ParsedRecord {
9
+ id: string;
7
10
  kind: string;
8
- pattern: string;
9
- flags?: string;
11
+ fields: Record<string, string | number | boolean | null>;
10
12
  }
11
- export interface ImportRule {
12
- kind: string;
13
- pattern: string;
14
- flags?: string;
15
- }
16
- export interface Snippet {
17
- description: string;
18
- body: string;
13
+ export interface Parsed {
14
+ format: string;
15
+ records: ParsedRecord[];
16
+ meta: Record<string, string | number | boolean | null>;
17
+ issues: Issue[];
19
18
  }
20
- export interface ScaffoldFile {
21
- path: string;
22
- content: string;
23
- }
24
- export interface LanguageConfig {
19
+ export interface FormatModule {
25
20
  slug: string;
26
21
  displayName: string;
27
- version: string;
28
22
  extensions: string[];
29
- markers: string[];
30
- comment: string;
31
- blockComment?: {
32
- start: string;
33
- end: string;
34
- };
35
- stringQuotes?: string[];
36
- allowTabs?: boolean;
37
- buildDir?: string;
38
- symbols: SymbolRule[];
39
- imports: ImportRule[];
40
- runner?: CommandSpec[];
41
- syntax?: CommandSpec;
42
- build?: CommandSpec[];
43
- test?: CommandSpec[];
44
- scaffold: ScaffoldFile[];
45
- snippets: Record<string, Snippet>;
23
+ description: string;
46
24
  reference: string;
25
+ parse(text: string): Parsed;
26
+ sample(): string;
27
+ regressions?: Array<{
28
+ doc: string;
29
+ expectError: string;
30
+ }>;
47
31
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrfentmen/openedge-abl-mcp",
3
- "version": "1.0.0",
4
- "description": "OpenEdge ABL MCP: 20 real tools to read, check, lint, scaffold, build and run OpenEdge ABL code.",
3
+ "version": "1.0.1",
4
+ "description": "OpenEdge MCP: parse ABL source, validate variables, blocks and statements.",
5
5
  "type": "module",
6
6
  "mcpName": "io.github.mrfentmen/openedge-abl-mcp",
7
7
  "repository": {
@@ -25,9 +25,11 @@
25
25
  "keywords": [
26
26
  "mcp",
27
27
  "model-context-protocol",
28
- "openedge-abl",
29
- "language",
30
- "toolchain"
28
+ "openedge",
29
+ "progress",
30
+ "abl",
31
+ "erp",
32
+ "4gl"
31
33
  ],
32
34
  "license": "MIT",
33
35
  "dependencies": {
package/server.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.mrfentmen/openedge-abl-mcp",
4
- "description": "OpenEdge ABL MCP: 20 real tools to read, check, lint, scaffold, build and run OpenEdge ABL code.",
4
+ "description": "OpenEdge MCP: parse ABL source, validate variables, blocks and statements.",
5
5
  "repository": {
6
- "url": "https://github.com/mrfentmen/awesome-servers/tree/main/mcp/servers/openedge-abl-mcp",
6
+ "url": "https://github.com/mrfentmen/awesome-servers",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.0.0",
9
+ "version": "1.0.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@mrfentmen/openedge-abl-mcp",
14
- "version": "1.0.0",
14
+ "version": "1.0.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }
package/dist/config.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { LanguageConfig } from "./types.js";
2
- export declare const config: LanguageConfig;
package/dist/config.js DELETED
@@ -1,47 +0,0 @@
1
- export const config = {
2
- "slug": "openedge-abl",
3
- "displayName": "OpenEdge ABL",
4
- "version": "1.0.0",
5
- "extensions": [
6
- ".p",
7
- ".cls",
8
- ".w"
9
- ],
10
- "markers": [],
11
- "comment": "/*",
12
- "stringQuotes": [
13
- "\""
14
- ],
15
- "symbols": [
16
- {
17
- "kind": "function",
18
- "pattern": "\\b(?:function|def|fn|fun|func|proc|procedure|sub|method|class|struct|enum|module|namespace|type|interface|trait|record|object)\\s+([A-Za-z_]\\w*)"
19
- },
20
- {
21
- "kind": "function",
22
- "pattern": "^\\s*(?:[A-Za-z_][\\w<>\\[\\]]*\\s+)+([A-Za-z_]\\w*)\\s*\\("
23
- },
24
- {
25
- "kind": "value",
26
- "pattern": "^([A-Za-z_]\\w*)\\s*(?::=|=|:)"
27
- }
28
- ],
29
- "imports": [],
30
- "scaffold": [
31
- {
32
- "path": "src/demo.p",
33
- "content": "DEFINE VARIABLE x AS INTEGER NO-UNDO.\n"
34
- },
35
- {
36
- "path": "tests/run.p",
37
- "content": "DEFINE VARIABLE x AS INTEGER NO-UNDO.\n"
38
- }
39
- ],
40
- "snippets": {
41
- "function": {
42
- "description": "function",
43
- "body": "DEFINE VARIABLE x AS INTEGER NO-UNDO.\n"
44
- }
45
- },
46
- "reference": "OpenEdge ABL syntax reference\n- Example: DEFINE VARIABLE x AS INTEGER NO-UNDO.\n- Comment: /*\n- No CLI runner configured"
47
- };