Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/great-bees-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-ux/fiori-mcp-server': minor
---

Add new tool get_fiori_rules to include default rules
24 changes: 13 additions & 11 deletions packages/fiori-mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,25 @@ The following rules help guide the LLM to use the server correctly:

```markdown
## Rules for creation or modification of SAP Fiori elements apps

- When asked to create an SAP Fiori elements app check whether the user input can be interpreted as an application organized into one or more pages containing table data or forms, these can be translated into a SAP Fiori elements application, else ask the user for suitable input.
- The application typically starts with a List Report page showing the data of the base entity of the application in a table. Details of a specific table row are shown in the ObjectPage. This first Object Page is therefore based on the base entity of the application.
- An Object Page can contain one or more table sections based on to-many associations of its entity type. The details of a table section row can be shown in an another Object Page based on the associations target entity.
- The data model must be suitable for usage in a SAP Fiori elements frontend application. So there must be one main entity and one or more navigation properties to related entities.
- Each property of an entity must have a proper datatype.
- For all entities in the data model provide primary keys of type UUID.
- When creating sample data in CSV files, all primary keys and foreign keys MUST be in UUID format (e.g., `550e8400-e29b-41d4-a716-446655440001`).
- When generating or modifying the SAP Fiori elements application on top of the CAP service use the Fiori MCP server if available.
- When attempting to modify the SAP Fiori elements application like adding columns you must not use the screen personalization but instead modify the code of the project, before this first check whether an MCP server provides a suitable function.
- When previewing the SAP Fiori elements application use the most specific `npm run watch-*` script for the app in the `package.json`.
- You MUST use call the get_fiori_rules tool from fiori mcp before creating or modifying fiori applications. You Do NOT propose, suggest or make any changes without first checking.
```

Add these rules to your existing global or project-specific [`AGENTS.md`](https://agents.md/) (specifics may vary based on respective MCP client).

## [Available Tools](#available-tools)

#### `get_fiori_rules`
Returns the complete set of rules and best practices for creating or modifying SAP Fiori elements applications.

Use this tool to understand constraints and guidelines for Fiori development, including:

- Application structure requirements (List Report, Object Pages)
- Data model requirements (entities, navigation properties, UUIDs)
- Sample data format requirements
- Preview and testing guidelines

This should be called when starting any Fiori application creation or modification task.

#### `search_docs`
Searches SAP Fiori elements, Annotations, UI5, SAP Fiori tools documentation for the given query.

Expand Down
23 changes: 23 additions & 0 deletions packages/fiori-mcp-server/esbuild.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { build } = require('esbuild');

const baseConfig = {
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'node',
target: 'node20',
outdir: 'dist',
external: ['vscode', '@lancedb/lancedb', '@xenova/transformers', '@sap-ux/fiori-docs-embeddings'],
mainFields: ['module', 'main'],
loader: {
'.md': 'text'
}
};

const isDev = process.argv.includes('--dev');
const isProd = process.argv.includes('--minify');

build({
...baseConfig,
sourcemap: isDev ? 'inline' : false,
minify: isProd
}).catch(() => process.exit(1));
4 changes: 4 additions & 0 deletions packages/fiori-mcp-server/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module.exports = {
...config,
modulePathIgnorePatterns: [...config.modulePathIgnorePatterns, '<rootDir>/test/data/'],
transformIgnorePatterns: ['<rootDir>/node_modules/(?!@xenova)'],
transform: {
...config.transform,
'\\.md$': '<rootDir>/test/__mocks__/markdown-transform.js'
},
moduleNameMapper: {
'^@lancedb/lancedb$': '<rootDir>/test/__mocks__/@lancedb/lancedb.js',
'^@xenova/transformers$': '<rootDir>/test/__mocks__/@xenova/transformers.js'
Expand Down
6 changes: 3 additions & 3 deletions packages/fiori-mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
"license": "Apache-2.0",
"main": "dist/index.js",
"scripts": {
"build-esbuild-base": "esbuild --bundle --platform=node --target=node20 --outdir=dist --external:vscode --external:@lancedb/lancedb --external:@xenova/transformers --external:@sap-ux/fiori-docs-embeddings --main-fields=module,main src/index.ts",
"build-dev": "pnpm run build-esbuild-base --sourcemap=inline",
"build-esbuild-base": "node esbuild.config.js",
"build-dev": "node esbuild.config.js --dev",
"build-compile": "tsc --noEmit",
"build-bundle": "pnpm run build-esbuild-base --minify",
"build-bundle": "node esbuild.config.js --minify",
"build": "npm-run-all build-compile build-bundle",
"watch": "tsc --watch",
"bundle": "npm-run-all build tgz:package tgz:rename",
Expand Down
12 changes: 12 additions & 0 deletions packages/fiori-mcp-server/src/rules/fiori-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Rules for creation or modification of SAP Fiori elements apps

- When asked to create an SAP Fiori elements app check whether the user input can be interpreted as an application organized into one or more pages containing table data or forms, these can be translated into a SAP Fiori elements application, else ask the user for suitable input.
- The application typically starts with a List Report page showing the data of the base entity of the application in a table. Details of a specific table row are shown in the ObjectPage. This first Object Page is therefore based on the base entity of the application.
- An Object Page can contain one or more table sections based on to-many associations of its entity type. The details of a table section row can be shown in an another Object Page based on the associations target entity.
- The data model must be suitable for usage in a SAP Fiori elements frontend application. So there must be one main entity and one or more navigation properties to related entities.
- Each property of an entity must have a proper datatype.
- For all entities in the data model provide primary keys of type UUID.
- When creating sample data in CSV files, all primary keys and foreign keys MUST be in UUID format (e.g., `550e8400-e29b-41d4-a716-446655440001`).
- When generating or modifying the SAP Fiori elements application on top of the CAP service use the Fiori MCP server if available.
- When attempting to modify the SAP Fiori elements application like adding columns you must not use the screen personalization but instead modify the code of the project, before this first check whether an MCP server provides a suitable function.
- When previewing the SAP Fiori elements application use the most specific `npm run watch-*` script for the app in the `package.json`.
10 changes: 10 additions & 0 deletions packages/fiori-mcp-server/src/rules/get-fiori-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import fioriRulesContent from './fiori-rules.md';

/**
* Returns the Fiori rules content bundled at build time.
*
* @returns The Fiori rules content as a string
*/
export function getFioriRules(): string {
return fioriRulesContent;
}
54 changes: 51 additions & 3 deletions packages/fiori-mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
type CallToolResult
} from '@modelcontextprotocol/sdk/types.js';
import packageJson from '../package.json';
import {
docSearch,
listFioriApps,
listFunctionalities,
getFunctionalityDetails,
executeFunctionality,
getFioriRules,
tools
} from './tools';
import { TelemetryHelper, unknownTool, type TelemetryData } from './telemetry';
Expand Down Expand Up @@ -48,12 +55,14 @@ export class FioriFunctionalityServer {
},
{
capabilities: {
tools: {}
tools: {},
prompts: {}
}
}
);

this.setupToolHandlers();
this.setupPromptHandlers();
this.setupErrorHandling();
}

Expand All @@ -76,6 +85,42 @@ export class FioriFunctionalityServer {
await TelemetryHelper.initTelemetrySettings();
}

/**
* Sets up handlers for MCP prompts.
* Configures handlers for listing and getting prompts with Fiori rules.
*/
private setupPromptHandlers(): void {
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: 'fiori-rules',
description:
'Complete set of rules and best practices for creating or modifying SAP Fiori elements applications'
}
]
};
});

this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name === 'fiori-rules') {
const rulesContent = getFioriRules();
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: rulesContent
}
}
]
};
}
throw new Error(`Unknown prompt: ${request.params.name}`);
});
}

/**
* Sets up handlers for various MCP tools.
* Configures handlers for listing tools, and calling specific Fiori functionality tools.
Expand Down Expand Up @@ -114,10 +159,13 @@ export class FioriFunctionalityServer {
case 'execute_functionality':
result = await executeFunctionality(args as ExecuteFunctionalityInput);
break;
case 'get_fiori_rules':
result = getFioriRules();
break;
default:
await TelemetryHelper.sendTelemetry(unknownTool, telemetryProperties, (args as any)?.appPath);
throw new Error(
`Unknown tool: ${name}. Try one of: list_fiori_apps, list_functionality, get_functionality_details, execute_functionality.`
`Unknown tool: ${name}. Try one of: list_fiori_apps, list_functionality, get_functionality_details, execute_functionality, get_fiori_rules.`
);
}
await TelemetryHelper.sendTelemetry(name, telemetryProperties, (args as any)?.appPath);
Expand Down
16 changes: 16 additions & 0 deletions packages/fiori-mcp-server/src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { listFioriApps } from './list-fiori-apps';
export { listFunctionalities } from './list-functionalities';
export { getFunctionalityDetails } from './get-functionality-details';
export { executeFunctionality } from './execute-functionality';
export { getFioriRules } from '../rules/get-fiori-rules';

export const tools = [
{
Expand Down Expand Up @@ -71,5 +72,20 @@ export const tools = [
You MUST provide the exact parameter information obtained from get_functionality_details (Step 2).`,
inputSchema: convertToSchema(Input.ExecuteFunctionalityInputSchema),
outputSchema: convertToSchema(Output.ExecuteFunctionalityOutputSchema)
},
{
name: 'get_fiori_rules',
description: `Returns the complete set of rules and best practices for creating or modifying SAP Fiori elements applications.
Use this tool to understand constraints and guidelines for Fiori development, including:
- Application structure requirements (List Report, Object Pages)
- Data model requirements (entities, navigation properties, UUIDs)
- Sample data format requirements
- Preview and testing guidelines
You MUST use this tool when starting any Fiori application creation or modification task.`,
inputSchema: {
type: 'object',
properties: {},
required: []
}
}
] as Tool[];
4 changes: 4 additions & 0 deletions packages/fiori-mcp-server/src/types/markdown.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.md' {
const content: string;
export default content;
}
13 changes: 13 additions & 0 deletions packages/fiori-mcp-server/test/__mocks__/markdown-transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { readFileSync } = require('fs');

module.exports = {
process(sourceText, sourcePath) {
// Read the actual markdown file content
const content = readFileSync(sourcePath, 'utf-8');

// Return as a CommonJS module that exports the content as default
return {
code: `module.exports = ${JSON.stringify(content)};`
};
}
};
85 changes: 81 additions & 4 deletions packages/fiori-mcp-server/test/unit/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ describe('FioriFunctionalityServer', () => {
// Check initialization
expect(Server).toHaveBeenCalledWith(
{ name: 'fiori-mcp', version: expect.any(String) },
{ capabilities: { tools: {} } }
{ capabilities: { tools: {}, prompts: {} } }
);
expect(setRequestHandlerMock).toHaveBeenCalledTimes(2);
expect(setRequestHandlerMock).toHaveBeenCalledTimes(4);
});

test('setup tools', async () => {
Expand All @@ -53,7 +53,8 @@ describe('FioriFunctionalityServer', () => {
'list_fiori_apps',
'list_functionality',
'get_functionality_details',
'execute_functionality'
'execute_functionality',
'get_fiori_rules'
]);
});

Expand Down Expand Up @@ -266,6 +267,31 @@ describe('FioriFunctionalityServer', () => {
);
});

test('get_fiori_rules', async () => {
const getFioriRulesSpy = jest.spyOn(tools, 'getFioriRules').mockReturnValue('# Rules for Fiori...');
new FioriFunctionalityServer();
const setRequestHandlerCall = setRequestHandlerMock.mock.calls[1];
const onRequestCB = setRequestHandlerCall[1];
const result = await onRequestCB({
params: {
name: 'get_fiori_rules',
arguments: {}
}
});
expect(getFioriRulesSpy).toHaveBeenCalledTimes(1);
expect(result.content).toEqual([
{
text: '# Rules for Fiori...',
type: 'text'
}
]);
expect(sendTelemetryMock).toHaveBeenLastCalledWith(
'get_fiori_rules',
{ tool: 'get_fiori_rules', functionalityId: undefined },
undefined
);
});

test('Unknown tool', async () => {
new FioriFunctionalityServer();
const setRequestHandlerCall = setRequestHandlerMock.mock.calls[1];
Expand All @@ -280,7 +306,7 @@ describe('FioriFunctionalityServer', () => {
});
expect(result.content).toEqual([
{
text: 'Error: Unknown tool: unknown-tool-id. Try one of: list_fiori_apps, list_functionality, get_functionality_details, execute_functionality.',
text: 'Error: Unknown tool: unknown-tool-id. Try one of: list_fiori_apps, list_functionality, get_functionality_details, execute_functionality, get_fiori_rules.',
type: 'text'
}
]);
Expand All @@ -295,6 +321,57 @@ describe('FioriFunctionalityServer', () => {
});
});

describe('Prompts', () => {
test('list_prompts', async () => {
new FioriFunctionalityServer();
const setRequestHandlerCall = setRequestHandlerMock.mock.calls[2];
const onRequestCB = setRequestHandlerCall[1];
const result = await onRequestCB();
expect(result.prompts).toEqual([
{
name: 'fiori-rules',
description:
'Complete set of rules and best practices for creating or modifying SAP Fiori elements applications'
}
]);
});

test('get_prompt - fiori-rules', async () => {
const getFioriRulesSpy = jest.spyOn(tools, 'getFioriRules').mockReturnValue('# Fiori Rules Content...');
new FioriFunctionalityServer();
const setRequestHandlerCall = setRequestHandlerMock.mock.calls[3];
const onRequestCB = setRequestHandlerCall[1];
const result = await onRequestCB({
params: {
name: 'fiori-rules'
}
});
expect(getFioriRulesSpy).toHaveBeenCalledTimes(1);
expect(result.messages).toEqual([
{
role: 'user',
content: {
type: 'text',
text: '# Fiori Rules Content...'
}
}
]);
});

test('get_prompt - unknown prompt', async () => {
new FioriFunctionalityServer();
const setRequestHandlerCall = setRequestHandlerMock.mock.calls[3];
const onRequestCB = setRequestHandlerCall[1];
await expect(
onRequestCB({
params: {
name: 'unknown-prompt'
}
})
).rejects.toThrow('Unknown prompt: unknown-prompt');
});
});

describe('Run', () => {
test('execute_functionality', async () => {
const server = new FioriFunctionalityServer();
Expand Down
Loading
Loading