Skip to content

Commit d6dffa8

Browse files
committed
feat(fiori-mcp-server): app-gen common schema
1 parent f8d44f6 commit d6dffa8

File tree

12 files changed

+103
-233
lines changed

12 files changed

+103
-233
lines changed

packages/fiori-mcp-server/src/tools/functionalities/fetch-systems/details.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/fiori-mcp-server/src/tools/functionalities/fetch-systems/execute-functionality.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/fiori-mcp-server/src/tools/functionalities/fetch-systems/index.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-app/command.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,17 @@
1+
import type { ExecuteFunctionalityInput, ExecuteFunctionalityOutput } from '../../../types';
2+
import type { GeneratorConfigCAP } from '../../schemas';
3+
import type { PackageInfo } from '@sap-ux/nodejs-utils';
4+
15
import { promises as FSpromises, existsSync } from 'node:fs';
26
import { promisify } from 'util';
37
import { exec as execAsync } from 'child_process';
48
import { dirname, join } from 'node:path';
5-
import type { ExecuteFunctionalityInput, ExecuteFunctionalityOutput } from '../../../types';
9+
import { findInstalledPackages } from '@sap-ux/nodejs-utils';
610
import { GENERATE_FIORI_UI_APP_ID } from '../../../constant';
7-
import { findInstalledPackages, type PackageInfo } from '@sap-ux/nodejs-utils';
8-
import packageJson from '../../../../package.json';
911
import { logger } from '../../../utils/logger';
10-
import { GeneratorConfigSchemaCAP, type GeneratorConfigCAP } from './schema';
12+
import { generatorConfigCAP } from '../../schemas';
1113
import { validateWithSchema } from '../../utils';
1214

13-
// Extended type generators API use
14-
const PREDEFINED_GENERATOR_VALUES = {
15-
// Config schema version
16-
version: '0.2',
17-
telemetryData: {
18-
'generationSourceName': packageJson.name,
19-
'generationSourceVersion': packageJson.version
20-
},
21-
project: {
22-
sapux: true
23-
}
24-
};
25-
type GeneratorConfigCAPWithAPI = GeneratorConfigCAP & typeof PREDEFINED_GENERATOR_VALUES;
26-
2715
const exec = promisify(execAsync);
2816

2917
/**
@@ -33,25 +21,13 @@ const exec = promisify(execAsync);
3321
* @returns Application generation execution output.
3422
*/
3523
export async function command(params: ExecuteFunctionalityInput): Promise<ExecuteFunctionalityOutput> {
36-
const generatorConfigCAP: GeneratorConfigCAP = validateWithSchema(GeneratorConfigSchemaCAP, params?.parameters);
37-
const generatorConfig: GeneratorConfigCAPWithAPI = {
38-
...PREDEFINED_GENERATOR_VALUES,
39-
...generatorConfigCAP,
40-
project: {
41-
...PREDEFINED_GENERATOR_VALUES.project,
42-
...generatorConfigCAP.project
43-
}
44-
};
24+
const generatorConfig: GeneratorConfigCAP = validateWithSchema(generatorConfigCAP, params?.parameters);
4525
generatorConfig.project.sapux = generatorConfig.floorplan !== 'FF_SIMPLE';
26+
4627
const projectPath = generatorConfig?.project?.targetFolder ?? params.appPath;
4728
if (!projectPath || typeof projectPath !== 'string') {
4829
throw new Error('Please provide a valid path to the CAP project folder.');
4930
}
50-
if (generatorConfig?.service.servicePath) {
51-
generatorConfig.service.servicePath = generatorConfig?.service.servicePath?.startsWith('/')
52-
? generatorConfig?.service.servicePath
53-
: `/${generatorConfig?.service.servicePath}`;
54-
}
5531
const appName = (generatorConfig?.project.name as string) ?? 'default';
5632
const appPath = join(projectPath, 'app', appName);
5733
const targetDir = projectPath;

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-app/generate-fiori-ui-app.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { command } from './command';
2-
import { GeneratorConfigSchemaCAP } from './schema';
3-
import { GENERATE_FIORI_UI_APP_ID } from '../../../constant';
41
import type {
52
ExecuteFunctionalityInput,
63
ExecuteFunctionalityOutput,
74
FunctionalityHandlers,
85
GetFunctionalityDetailsOutput
96
} from '../../../types';
10-
import { convertToSchema } from '../../utils';
7+
8+
import { command } from './command';
9+
import { generatorConfigCAPJson as parameters } from '../../schemas';
10+
import { GENERATE_FIORI_UI_APP_ID } from '../../../constant';
1111

1212
export const GENERATE_FIORI_UI_APP: GetFunctionalityDetailsOutput = {
1313
functionalityId: GENERATE_FIORI_UI_APP_ID,
@@ -18,7 +18,7 @@ export const GENERATE_FIORI_UI_APP: GetFunctionalityDetailsOutput = {
1818
The data obtained from either method must then be formatted into a JSON object and passed as the parameters.
1919
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the tool.
2020
The configuration **MUST** be based on the project files in the projectPath.`,
21-
parameters: convertToSchema(GeneratorConfigSchemaCAP)
21+
parameters
2222
};
2323

2424
/**

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-app/schema.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
import type { GetFunctionalityDetailsOutput } from '../../../types';
2-
import { generatorConfigSchemaNonCAPJson } from './schema';
2+
import { generatorConfigODataJson as parameters } from '../../schemas';
33

44
export default {
55
functionalityId: 'generate-fiori-ui-odata-app',
6-
name: 'Generate SAP Fiori UI Application for non-CAP Projects',
6+
name: 'Generate SAP Fiori UI Application for OData Projects (non-CAP)',
77
description: `Creates (generates) a new SAP Fiori UI application within an existing project (RAP or other non-CAP).
88
Crucially, you must first construct the appGenConfig JSON argument.
9-
10-
If the user has not provided a valid servicePath and host (URL) of the OData service they want to use,
11-
you **MUST** first retrieve the list of user stored SAP systems (using the "fetch-system" functionality with "execute_functionality" tool),
12-
13-
then match the provided name to one of the systems (or ask the user to choose from a list if multiple match),
14-
and then retrieve the list of services from that system (using the "fetch-system-services" functionality), and ask the user to select one of the services,
15-
and finally retrieve the corresponding servicePath, host, and client from the selected service.
16-
9+
If the user has not provided a valid servicePath and host (URL) of the OData service they want to use, you **MUST** ask for it.
1710
Next you **MUST** query the service metadata endpoint to retrieve the list of available entities.
1811
(**IMPORTANT**: service metadata endpoint URL must end with "$metadata". If the provided servicePath does not end with "$metadata", you **MUST** append it.)
19-
(**IMPORTANT**: if the service requires authentication, you should use the same username and password that the "fetch-system" functionality returned with the sap-system that the service belongs to.)
20-
12+
(**IMPORTANT**: if the service requires authentication run 'curl' command to fetch the edmx xml response and allow the user to authenticate if needed).
2113
Lastly, using the service edmx (response xml from <serviceurl>/$metadata request), figure out the data model structure, entities, and associations.`,
22-
parameters: generatorConfigSchemaNonCAPJson
14+
parameters
2315
} as GetFunctionalityDetailsOutput;

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-odata-app/execute-functionality.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
/* eslint-disable no-console */
22
import type { ExecuteFunctionalityInput, ExecuteFunctionalityOutput } from '../../../types';
3-
import type { NonCAPSchema, GeneratorConfigNonCAP } from './schema';
3+
import type { GeneratorConfigOData } from '../../schemas';
44

55
import { promises, existsSync } from 'fs';
66
import { dirname, join } from 'path';
77
import { exec as execAsync } from 'child_process';
88
import { promisify } from 'util';
9-
import { GeneratorConfigSchemaNonCAP } from './schema';
10-
import details from './details';
9+
import { generatorConfigOData } from '../../schemas';
1110
import { validateWithSchema } from '../../utils';
12-
import packageJson from '../../../../package.json';
11+
import details from './details';
1312

1413
const exec = promisify(execAsync);
1514

@@ -20,15 +19,7 @@ const exec = promisify(execAsync);
2019
* @returns Application generation execution output.
2120
*/
2221
export default async function (params: ExecuteFunctionalityInput): Promise<ExecuteFunctionalityOutput> {
23-
const nonCAPConfig: NonCAPSchema = validateWithSchema(GeneratorConfigSchemaNonCAP, params?.parameters);
24-
const generatorConfig: GeneratorConfigNonCAP = nonCAPConfig?.appGenConfig;
25-
26-
generatorConfig.telemetryData = {
27-
generationSourceName: packageJson.name,
28-
generationSourceVersion: packageJson.version
29-
};
30-
generatorConfig.project.sapux = generatorConfig.floorplan !== 'FF_SIMPLE';
31-
22+
const generatorConfig: GeneratorConfigOData = validateWithSchema(generatorConfigOData, params?.parameters);
3223
const projectPath = generatorConfig?.project?.targetFolder ?? params.appPath;
3324

3425
if (!projectPath || typeof projectPath !== 'string') {
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
11
import * as z from 'zod';
2-
import { LATEST_UI5_VERSION } from '../../../constant';
3-
import { convertToSchema } from '../../utils';
2+
import { LATEST_UI5_VERSION } from '../../constant';
3+
import packageJson from '../../../package.json';
44

5-
const projectPath = z
6-
.string()
7-
.describe('The path to the non-CAP project folder. By default the currently opened project folder should be used.');
5+
export const version = z.string().default('0.2').describe('Config schema version.');
86

9-
const version = z.string().default('0.2').describe('Config schema version.');
10-
11-
const floorplan = z
7+
export const floorplan = z
128
.literal(['FE_FPM', 'FE_LROP', 'FE_OVP', 'FE_ALP', 'FE_FEOP', 'FE_WORKLIST', 'FF_SIMPLE'])
139
.describe('SAP Fiori Elements floor plan type.');
1410

15-
const projectType = z
11+
export const projectType = z
1612
.literal(['LIST_REPORT_OBJECT_PAGE', 'FORM_ENTRY_OBJECT_PAGE', 'FLEXIBLE_PROGRAMMING_MODEL'])
1713
.describe('SAP Fiori Elements project type. Corresponds to the SAP Fiori Elements floor plan.');
1814

19-
const project = z.object({
15+
export const project = z.object({
2016
name: z
2117
.string()
2218
.describe("Must be lowercase with dashes, e.g., 'sales-order-management'.")
2319
.regex(/^[a-z0-9-]+$/),
2420
title: z.optional(z.string()),
2521
description: z.string(),
26-
targetFolder: z.string().describe('Absolute path to the non-CAP project folder (projectPath).'),
22+
targetFolder: z.string().describe('Absolute path to the project folder (projectPath).'),
2723
ui5Version: z.string().default(LATEST_UI5_VERSION),
2824
sapux: z.boolean().default(true)
2925
});
3026

31-
const service = z.object({
27+
export const serviceOdata = z.object({
3228
servicePath: z
3329
.string()
34-
.describe(
35-
'The odata endpoint as provided by the cds mcp. If the parameter is not provided, ' +
36-
'the agent should ask the user for it. Some service endpoints require authentication ' +
37-
'- in that case, the agent should ask the user for the necessary credentials.'
38-
)
30+
.describe('The odata endpoint. If the parameter is not provided, the agent should ask the user for it.')
3931
.meta({
4032
examples: [
4133
'odata/v4/<servicename>/',
@@ -56,7 +48,26 @@ const service = z.object({
5648
edmx: z.string().describe('The service metadata in the stringified XML format.')
5749
});
5850

59-
const entityConfig = z.object({
51+
export const serviceCap = z.object({
52+
capService: z.object({
53+
projectPath: z.string(),
54+
serviceName: z.string(),
55+
serviceCdsPath: z
56+
.string()
57+
.describe('The path to the service cds file')
58+
.meta({
59+
examples: [
60+
'srv/service.cds',
61+
'srv/my-service.cds',
62+
'path/to/srv/service.cds',
63+
'path/to/srv/my-service.cds'
64+
]
65+
}),
66+
capType: z.optional(z.literal(['Node.js', 'Java']))
67+
})
68+
});
69+
70+
export const entityConfig = z.object({
6071
mainEntity: z.object({
6172
entityName: z
6273
.string()
@@ -67,26 +78,7 @@ const entityConfig = z.object({
6778
generateLROPAnnotations: z.boolean()
6879
});
6980

70-
const telemetryData = z.object({
71-
generationSourceName: z.string().default('AI Headless MCP'),
72-
generationSourceVersion: z.string().default('1.0.0')
81+
export const telemetryData = z.object({
82+
generationSourceName: z.string().default(packageJson.name),
83+
generationSourceVersion: z.string().default(packageJson.version)
7384
});
74-
75-
const appGenConfig = z.object({
76-
version,
77-
floorplan,
78-
projectType,
79-
project,
80-
service,
81-
entityConfig,
82-
telemetryData
83-
}).describe(`The configuration that will be used for the Application UI generation.
84-
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the tool.
85-
The configuration **MUST** be based on the project files in the projectPath (if a project exists).`);
86-
87-
export const GeneratorConfigSchemaNonCAP = z.object({ projectPath, appGenConfig });
88-
89-
export const generatorConfigSchemaNonCAPJson = convertToSchema(GeneratorConfigSchemaNonCAP);
90-
91-
export type NonCAPSchema = z.infer<typeof GeneratorConfigSchemaNonCAP>;
92-
export type GeneratorConfigNonCAP = z.infer<typeof appGenConfig>;

0 commit comments

Comments
 (0)