Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,498 changes: 1,183 additions & 315 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"license": "MIT",
"workspaces": [
"packages/langium-ai-tools",
"packages/langium-ai-mcp",
"packages/examples/*"
],
"volta": {
Expand Down
3 changes: 2 additions & 1 deletion packages/examples/example-dsl-evaluator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "tsc",
"start": "node ./dist/index.js",
"demo": "npm run build && npm run start -- run-langdev && open ./radar-chart.html",
"clean": "rimraf ./dist"
"clean": "rimraf ./dist",
"test": "echo \"No tests yet...\""
},
"type": "module",
"author": {
Expand Down
21 changes: 21 additions & 0 deletions packages/langium-ai-mcp/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
How to try-out:

- `cd packages/langium-ai-mcp`
- Start MCP server with IO transport `npm run start`
- Run example client code `npm run cstart` - you should see the tool result containing the errors.

Example usage in Cursor:

- Open Cursor MCP settings
- Add new server using following setup (user or workspace specific `.cursor/mcp.json` ):

```json
"mcpServers": {
"Langium MCP": {
"command": "node",
"args": [
"~/git/langium-ai/packages/mcp-server/dist/mcp-server.js"
]
}
}
```
52 changes: 52 additions & 0 deletions packages/langium-ai-mcp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "langium-ai-mcp",
"version": "0.0.2",
"displayName": "Langium AI - MCP",
"publisher": "TypeFox",
"description": "MCP server for Langium AI",
"repository": {
"type": "git",
"url": "git+https://github.com/eclipse-langium/langium-ai.git",
"directory": "packages/langium-ai-mcp"
},
"bugs": "https://github.com/eclipse-langium/langium-ai/issues",
"type": "module",
"main": "dist/mcp-server.js",
"private": false,
"files": [
"dist"
],
"scripts": {
"clean": "rm -rf ./dist",
"build": "npm run clean && tsc",
"watch": "tsc -w",
"start": "node .",
"cstart": "node ./dist/mcp-client.js",
"prepare": "npm run build",
"test": "vitest run"
},
"author": {
"name": "TypeFox",
"url": "https://www.typefox.io"
},
"keywords": [
"langium",
"ai",
"mcp",
"server",
"llm"
],
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.4",
"langium-ai-tools": "0.0.2"
},
"volta": {
"node": "20.10.0",
"npm": "10.2.3"
},
"devDependencies": {
"typescript": "^5.4.5",
"vitest": "^3.0.9"
}
}
39 changes: 39 additions & 0 deletions packages/langium-ai-mcp/src/mcp-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";

const debug = false

const transport = new StdioClientTransport({
command: "node",
args: [ "./dist/mcp-server.js"]
});

const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);

await client.connect(transport);

const tools = await client.listTools();
console.log("Available tools:", "\n", ...tools.tools.map(t => getDisplayName(t) + "\n"));

const theTool = tools.tools[0];
if (!theTool) {
throw new Error("No tool available");
}

const result = await client.callTool({
name: theTool.name,
arguments: {
code: 'syntax error'
}
});

console.log("Tool result:", result.content);

// exit the process
process.exit(0);
60 changes: 60 additions & 0 deletions packages/langium-ai-mcp/src/mcp-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { LangiumEvaluator, type LangiumEvaluatorResultData } from 'langium-ai-tools';
import { createLangiumGrammarServices } from 'langium/grammar';

import { NodeFileSystem } from 'langium/node';
import { z } from 'zod';

const server = new McpServer({
name: 'langium-mpc-server',
version: '1.0.0'
});

server.registerTool('langium-syntax-checker',
{
title: 'Langium Evaluator Tool',
description: 'Checks Langium code for errors',
inputSchema: { code: z.string() }
},
async ({ code }) => {
const validationResult = await validateLangiumCode(code);
return {
content: [
{
type: 'text',
text: validationResult ?? 'The provided Langium code has no issues.'
}
]
}
}
);

export const langiumEvaluator = new LangiumEvaluator(createLangiumGrammarServices(NodeFileSystem).grammar);

export async function validateLangiumCode(code: string): Promise<string | undefined> {
const evalResult = await langiumEvaluator.evaluate(code);
if (evalResult.data) {
const langiumData = evalResult.data as LangiumEvaluatorResultData;
if (langiumData.diagnostics.length > 0) {
return langiumData.diagnostics.map(d =>
`${asText(d.severity)}: ${d.message} at line ${d.range.start.line + 1}, column ${d.range.start.character + 1}`
).join('\n');
}
}
return undefined;
}

function asText(severity: number | undefined): string {

switch (severity) {
case 1: return 'Error';
case 2: return 'Warning';
case 3: return 'Information';
case 4: return 'Hint';
default: return 'Unknown';
}
}

const transport = new StdioServerTransport();
await server.connect(transport);
32 changes: 32 additions & 0 deletions packages/langium-ai-mcp/tests/mcp-server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';

import { validateLangiumCode } from '../src/mcp-server';

describe('validateLangiumCode', () => {


it('should return undefined for valid grammar code', async () => {
const validCode = `
grammar HelloWorld

entry Model: persons+=Person*;
Person: 'person' name=ID;
hidden terminal WS: /\\s+/;
terminal ID: /[_a-zA-Z][\\w_]*/;
`;

const result = await validateLangiumCode(validCode);
expect(result).toBeUndefined();
});

it('should return diagnostics for invalid grammar code', async () => {
const invalidCode = `
grammar HelloWorld
entry Model: persons+=Person*;
`;

const result = await validateLangiumCode(invalidCode);
expect(result).toBeDefined();
expect(result).toContain("Error: Could not resolve reference to AbstractRule named 'Person'. at line 3, column 35");
});
});
32 changes: 32 additions & 0 deletions packages/langium-ai-mcp/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"module": "nodenext",
"target": "esnext",
"types": [
"node"
],
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"tests",
"dist"
]
}
14 changes: 14 additions & 0 deletions packages/langium-ai-mcp/tsconfig.tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["vitest/globals", "node"]
},
"include": [
"src/**/*",
"tests/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
17 changes: 17 additions & 0 deletions packages/langium-ai-mcp/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
environment: 'node',
globals: true,
include: ['tests/**/*.test.ts'],
typecheck: {
tsconfig: 'tsconfig.tests.json'
}
},
esbuild: {
target: 'node18'
}
});


2 changes: 1 addition & 1 deletion packages/langium-ai-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "vitest run",
"test": "vitest run --passWithNoTests",
"clean": "rimraf ./dist"
},
"author": {
Expand Down