Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
90dacb2
feat: Add manifest validation tool
dimovpetar Oct 21, 2025
fc51780
refactor: Move manifest schema fetching to ui5Manifest.ts
dimovpetar Oct 21, 2025
e32b101
test(index.ts): Add tests
dimovpetar Oct 22, 2025
a5992fc
refactor(ui5Manifest.ts): Cache the schema
dimovpetar Oct 22, 2025
e5ecb59
docs(README.md): List run_manifest_validation
dimovpetar Oct 27, 2025
7ef41ca
refactor(ui5Manifest): Add cache
dimovpetar Oct 27, 2025
b2ed9a3
refactor: Improve error handling
dimovpetar Oct 27, 2025
e9009eb
test(runValidation): Add tests
dimovpetar Oct 27, 2025
51e2239
refactor: Add comment containing link to AdaptiveCards issue
dimovpetar Oct 30, 2025
0acf46b
fix(package.json): List ajv as dependency
dimovpetar Oct 30, 2025
67db4fb
fix(runValidation): Resolve meta schemas paths using import.meta.resolve
dimovpetar Oct 30, 2025
86873ea
fix(runValidation): Throw error if manifest path is not absolute
dimovpetar Oct 30, 2025
3b7ab28
fix(run_manifest_validation): Normalize manifest path
dimovpetar Nov 5, 2025
626675b
refactor: Fetch concrete manifest schema
dimovpetar Nov 10, 2025
71032b2
fix(runValidation): Properly release mutex
dimovpetar Nov 17, 2025
ee562f3
fix(ui5Manifest): Throw errors for versions lt 1.68.0
dimovpetar Nov 17, 2025
e633b9d
fix(runValidation): Include ajv-formats
dimovpetar Nov 17, 2025
86ec801
refactor(ui5Manifest): Enhance more errors with supported versions info
dimovpetar Nov 17, 2025
595268b
test(runValidation): Increase coverage for external schemas
dimovpetar Nov 24, 2025
d1b8290
refactor(runValidation): Add explanation why meta schemas are needed
dimovpetar Nov 24, 2025
9bae0aa
refactor(runValidation): Add explanation for unicodeRegExp setting
dimovpetar Nov 24, 2025
615ef9e
refactor(runValidation): Add coment why strict flag of ajv is off
dimovpetar Nov 24, 2025
7e58636
refactor(ui5Manifest): Add comments for lowest supported version 1.68.0
dimovpetar Nov 24, 2025
31e4aac
fix(run_manifest_validation): Mark the tool as read-only
dimovpetar Nov 25, 2025
e41177c
refactor(runValidation): Remove undefined properties from the result
dimovpetar Nov 25, 2025
a068ff0
fix(schema.ts): Resolve ts error
dimovpetar Dec 19, 2025
2f3e782
docs(architecture.md): Describe all cards tools
dimovpetar Dec 19, 2025
e68d9e1
fix(ui5Manifest.ts): Avoid duplicate entries
dimovpetar Dec 19, 2025
02c51e3
refactor: Use new UI5 Manifest repo URL
matz3 Dec 19, 2025
c7e5dbe
test: Add test for formatters
dimovpetar Dec 19, 2025
e4d45f9
docs: Improve wording
dimovpetar Dec 19, 2025
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 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The UI5 [Model Context Protocol](https://modelcontextprotocol.io/) server offers
- `get_typescript_conversion_guidelines`: Provides guidelines for converting UI5 applications and controls from JavaScript to TypeScript.
- `get_integration_cards_guidelines`: Provides access to UI Integration Cards development best practices.
- `create_integration_card`: Scaffolds a new UI Integration Card.
- `run_manifest_validation`: Validates the manifest against the UI5 Manifest schema.

## Requirements

Expand Down
114 changes: 114 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ Tool Name | Description
`get_typescript_conversion_guidelines` | Provides guidelines for converting UI5 applications and controls from JavaScript to TypeScript
`get_version_info` | Retrieves version information for the UI5 framework
`run_ui5_linter` | Integrates with `@ui5/linter` to analyze and report issues in UI5 code
`run_manifest_validation` | Validates the manifest against the UI5 Manifest schema

### create_integration_card

The `create_integration_card` tool is designed to scaffold new UI Integration Cards using predefined templates. It automates the setup process, ensuring that developers can quickly start building integration cards without manually configuring the project structure.

Templates are stored in the `resources/` directory of the project. There is currently one template for every declarative card type (List Card, Table Card, etc.).

For rendering the templates with the provided data, the [EJS](https://github.com/mde/ejs) templating engine is used.

### create_ui5_app

Expand Down Expand Up @@ -187,6 +196,10 @@ Drawbacks of the current approach using the semantic model:

The `get_guidelines` tool returns a single markdown resource containing best practices and guidelines for UI5 development, particularly targeted towards AI agents. The document can be found in the `resources/` directory of the project.

### get_integration_cards_guidelines

The `get_integration_cards_guidelines` tool returns a single markdown resource containing best practices and guidelines for UI Integration Cards development. The content is particularly targeted towards AI agents. This document can be found in the `resources/` directory of the project.

### get_project_info

The `get_project_info` tool extracts metadata and configuration from a UI5 project. It provides insights into the project's structure, dependencies, and configuration settings, helping developers understand the context of their application.
Expand Down Expand Up @@ -485,6 +498,107 @@ In the future, these guides should be moved into the UI5 linter project. See als
}
```

### run_manifest_validation

The `run_manifest_validation` tool provides comprehensive schema validation for UI5 manifest files (`manifest.json`). It ensures that manifest files conform to the official UI5 Manifest JSON Schema, helping developers catch configuration errors early in the development process.

#### Overview

This tool uses the [Ajv JSON schema validator](https://www.npmjs.com/package/ajv) (specifically Ajv 2020-12) to perform validation against the official manifest schema. The schema is dynamically fetched from the [UI5 Manifest repository](https://github.com/UI5/manifest) based on the `_version` property declared in the manifest file.

#### Schema Management

**Version Detection:**
- The tool automatically detects the manifest version from the `_version` property
- If the `_version` property is missing, malformed, or not a valid semantic version, validation fails with a helpful error message listing supported versions
- The minimum supported manifest version is **1.68.0** (earlier versions use incompatible meta-schemas)

**Schema Retrieval:**
- Schemas are fetched from: `https://raw.githubusercontent.com/UI5/manifest/v{version}/schema.json`
- A version mapping is maintained at: `https://raw.githubusercontent.com/UI5/manifest/main/mapping.json`
- Schemas are cached locally after first fetch to improve performance and reduce network requests
- External schemas referenced by the UI5 manifest schema (e.g., Adaptive Card schema) are also fetched and cached as needed

#### Validation Process

- Reads the manifest file from the provided absolute path
- Parses the JSON content and extracts the `_version` property
- Fetches the corresponding schema based on the version
- Uses Ajv to validate the manifest against the schema
- Returns a detailed report of validation results, including specific error messages for any violations found

#### Performance Characteristics

**Caching Strategy:**
- Schema files are cached in memory after first retrieval
- Cache is shared across multiple validation calls in the same process
- Mutex locks prevent duplicate concurrent downloads of the same schema
- Network requests are only made once per schema version per process lifecycle

#### Error Handling

**Input Errors:**
- Non-absolute paths: `InvalidInputError` with clear message
- File not found: `InvalidInputError` indicating the file doesn't exist
- Invalid JSON: `InvalidInputError` with JSON parsing error details
- Missing `_version`: Detailed error with list of supported versions
- Unsupported version: Error message with version requirements and supported versions list

**Network Errors:**
- Schema fetch failures are caught and reported with helpful context
- The tool provides fallback error messages even if the supported versions list cannot be fetched
- Cached schemas allow continued operation even if the network is unavailable after initial setup

#### Example Input

```json
{
"manifestPath": "/absolute/path/to/project/webapp/manifest.json"
}
```

#### Example Output for Invalid Manifest

```json
{
"isValid": false,
"errors": [
{
"keyword": "required",
"instancePath": "",
"schemaPath": "#/required",
"params": {
"missingProperty": "sap.ui"
},
"message": "must have required property 'sap.ui'"
},
{
"keyword": "required",
"instancePath": "/sap.app",
"schemaPath": "#/properties/sap.app/required",
"params": {
"missingProperty": "title"
},
"message": "must have required property 'title'"
}
]
}
```

#### Example Output for Valid Manifest

```json
{
"isValid": true,
"errors": []
}
```

#### Requirements

- **Network Access**: Initial schema fetch requires internet connectivity
- **Manifest Versioning**: The manifest must declare a valid `_version` property

## Resources

### UI5 Documentation
Expand Down
2 changes: 2 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
"@ui5/linter": "^1.20.6",
"@ui5/logger": "^4.0.2",
"@ui5/project": "^4.0.9",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"async-mutex": "^0.5.0",
"ejs": "^3.1.10",
"execa": "^9.6.1",
Expand Down
1 change: 1 addition & 0 deletions resources/integration_cards_guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
## 2. Validation
- **ALWAYS** ensure that `manifest.json` file is valid JSON.
- **ALWAYS** ensure that in `manifest.json` file the property `sap.app/type` is set to `"card"`.
- **ALWAYS** validate the `manifest.json` against the UI5 Manifest schema. Use the `run_manifest_validation` tool to do this.
- **ALWAYS** avoid using deprecated properties in `manifest.json` and elsewhere.
- **NEVER** treat Integration Cards' project as UI5 project, except for cards of type "Component".

Expand Down
3 changes: 3 additions & 0 deletions src/registerTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import registerGetGuidelinesTool from "./tools/get_guidelines/index.js";
import registerGetVersionInfoTool from "./tools/get_version_info/index.js";
import registerGetIntegrationCardsGuidelinesTool from "./tools/get_integration_cards_guidelines/index.js";
import registerCreateIntegrationCardTool from "./tools/create_integration_card/index.js";
import registerRunManifestValidationTool from "./tools/run_manifest_validation/index.js";
import registerGetTypescriptConversionGuidelinesTool from "./tools/get_typescript_conversion_guidelines/index.js";

interface Options {
Expand Down Expand Up @@ -56,6 +57,8 @@ export default function (server: McpServer, context: Context, options: Options)

registerCreateIntegrationCardTool(registerTool, context);

registerRunManifestValidationTool(registerTool, context);

registerGetTypescriptConversionGuidelinesTool(registerTool, context);
}

Expand Down
36 changes: 36 additions & 0 deletions src/tools/run_manifest_validation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import runValidation from "./runValidation.js";
import {inputSchema, outputSchema} from "./schema.js";
import {getLogger} from "@ui5/logger";
import Context from "../../Context.js";
import {RegisterTool} from "../../registerTools.js";

const log = getLogger("tools:run_manifest_validation");

export default function registerTool(registerTool: RegisterTool, context: Context) {
registerTool("run_manifest_validation", {
title: "Manifest Validation",
description:
"Validates UI5 manifest file. " +
"After making changes, you should always run the validation again " +
"to verify that no new problems have been introduced.",
annotations: {
title: "Manifest Validation",
readOnlyHint: true,
},
inputSchema,
outputSchema,
}, async ({manifestPath}) => {
log.info(`Running manifest validation on ${manifestPath}...`);

const normalizedManifestPath = await context.normalizePath(manifestPath);
const result = await runValidation(normalizedManifestPath);

return {
content: [{
type: "text",
text: JSON.stringify(result),
}],
structuredContent: result,
};
});
}
155 changes: 155 additions & 0 deletions src/tools/run_manifest_validation/runValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {fetchCdn} from "../../utils/cdnHelper.js";
import {RunSchemaValidationResult} from "./schema.js";
import Ajv2020, {AnySchemaObject} from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import {readFile} from "fs/promises";
import {getLogger} from "@ui5/logger";
import {InvalidInputError} from "../../utils.js";
import {getManifestSchema, getManifestVersion} from "../../utils/ui5Manifest.js";
import {Mutex} from "async-mutex";
import {fileURLToPath} from "url";
import {isAbsolute} from "path";

const log = getLogger("tools:run_manifest_validation:runValidation");
const schemaCache = new Map<string, AnySchemaObject>();
const fetchSchemaMutex = new Mutex();

const AJV_SCHEMA_PATHS = {
draft06: fileURLToPath(import.meta.resolve("ajv/dist/refs/json-schema-draft-06.json")),
draft07: fileURLToPath(import.meta.resolve("ajv/dist/refs/json-schema-draft-07.json")),
} as const;

async function createUI5ManifestValidateFunction(ui5Schema: object) {
try {
const ajv = new Ajv2020.default({
// Collect all errors, not just the first one
allErrors: true,
// Allow additional properties that are not in schema such as "i18n",
// otherwise compilation fails
strict: false,
// Don't use Unicode-aware regular expressions,
// otherwise compilation fails with "Invalid escape" errors
unicodeRegExp: false,
loadSchema: async (uri) => {
const release = await fetchSchemaMutex.acquire();

try {
if (schemaCache.has(uri)) {
log.info(`Loading cached schema: ${uri}`);
return schemaCache.get(uri)!;
}

log.info(`Loading external schema: ${uri}`);
const schema = await fetchCdn(uri) as AnySchemaObject;

// Special handling for Adaptive Card schema to fix unsupported "id" property
// According to the JSON Schema spec Draft 06 (used by Adaptive Card schema),
// "$id" should be used instead of "id"
// See https://github.com/microsoft/AdaptiveCards/issues/9274
if (uri.includes("adaptive-card.json") && typeof schema.id === "string") {
schema.$id = schema.id;
delete schema.id;
}

schemaCache.set(uri, schema);

return schema;
} catch (error) {
log.warn(`Failed to load external schema ${uri}:` +
`${error instanceof Error ? error.message : String(error)}`);

throw error;
} finally {
release();
}
},
});

addFormats.default(ajv);

const draft06MetaSchema = JSON.parse(
await readFile(AJV_SCHEMA_PATHS.draft06, "utf-8")
) as AnySchemaObject;
const draft07MetaSchema = JSON.parse(
await readFile(AJV_SCHEMA_PATHS.draft07, "utf-8")
) as AnySchemaObject;

// Add meta-schemas for draft-06 and draft-07.
// These are required to support schemas that reference these drafts,
// for example the Adaptive Card schema and some sap.bpa.task properties.
ajv.addMetaSchema(draft06MetaSchema, "http://json-schema.org/draft-06/schema#");
ajv.addMetaSchema(draft07MetaSchema, "http://json-schema.org/draft-07/schema#");

const validate = await ajv.compileAsync(ui5Schema);

return validate;
} catch (error) {
throw new Error(`Failed to create UI5 manifest validate function: ` +
`${error instanceof Error ? error.message : String(error)}`);
}
}

async function readManifest(path: string) {
let content: string;
let json: object;

if (!isAbsolute(path)) {
throw new InvalidInputError(`The manifest path must be absolute: '${path}'`);
}

try {
content = await readFile(path, "utf-8");
} catch (error) {
throw new InvalidInputError(`Failed to read manifest file at ${path}: ` +
`${error instanceof Error ? error.message : String(error)}`);
}

try {
json = JSON.parse(content) as object;
} catch (error) {
throw new InvalidInputError(`Failed to parse manifest file at ${path} as JSON: ` +
`${error instanceof Error ? error.message : String(error)}`);
}

return json;
}

export default async function runValidation(manifestPath: string): Promise<RunSchemaValidationResult> {
log.info(`Starting manifest validation for file: ${manifestPath}`);

const manifest = await readManifest(manifestPath);
const manifestVersion = await getManifestVersion(manifest);
log.info(`Using manifest version: ${manifestVersion}`);
const ui5ManifestSchema = await getManifestSchema(manifestVersion);
const validate = await createUI5ManifestValidateFunction(ui5ManifestSchema);
const isValid = validate(manifest);

if (isValid) {
log.info("Manifest validation successful");

return {
isValid: true,
errors: [],
};
}

// Map AJV errors to our schema format
const validationErrors = validate.errors ?? [];
const errors = validationErrors.map((error): RunSchemaValidationResult["errors"][number] => {
return {
keyword: error.keyword ?? "",
instancePath: error.instancePath ?? "",
schemaPath: error.schemaPath ?? "",
params: error.params ?? {},
propertyName: error.propertyName,
message: error.message,
};
});

log.info(`Manifest validation failed with ${errors.length} error(s)`);

return {
isValid: false,
errors: errors,
};
}
Loading