Skip to content

Instantly share code, notes, and snippets.

@guillaumegarcia13
Created June 22, 2025 16:12
Show Gist options
  • Select an option

  • Save guillaumegarcia13/be219c1dd32b731e616f5f1d21fdbfcb to your computer and use it in GitHub Desktop.

Select an option

Save guillaumegarcia13/be219c1dd32b731e616f5f1d21fdbfcb to your computer and use it in GitHub Desktop.
Dynamic MCP server for ABAP class
"! <p class="shorttext synchronized" lang="en">HRC Software - MCP Server - Dynamic</p>
class /AHRC/CL_MCP_DYNAMIC definition
public
inheriting from ZCL_MCP_SERVER_BASE
final
create public .
public section.
types:
BEGIN OF ty_s_method_details,
class TYPE seocompodf-clsname,
method TYPE seocompodf-cmpname,
visibility TYPE seocompodf-exposure, " 0 Private, 1 Protected, 2 Public
declaration_level TYPE seocompodf-mtddecltyp, " 0 Instance, 1 Static method
is_abstract TYPE seocompodf-mtdabstrct, " 'X' abstract
description TYPE seocompotx-descript,
END OF ty_s_method_details .
data REGISTERED_METHODS type SEO_CMPKEYS read-only .
methods CONSTRUCTOR .
protected section.
methods GET_METHOD_DETAILS
importing
!CLASS type SEOCLSNAME
!METHOD type SEOCMPNAME
!LANGUAGE type SY-LANGU default 'E'
returning
value(METHOD_DETAILS) type TY_S_METHOD_DETAILS .
methods _BUILD_DYNAMIC_SCHEMA_OLD
importing
!CLASS type SEOCLSNAME
!METHOD type SEOCMPNAME
!LANGUAGE type SY-LANGU default 'E'
returning
value(SCHEMA) type ref to ZCL_MCP_SCHEMA_BUILDER .
methods BUILD_DYNAMIC_SCHEMA
importing
!CLASS type SEOCLSNAME
!METHOD type SEOCMPNAME
!LANGUAGE type SY-LANGU default 'E'
returning
value(SCHEMA) type ref to ZCL_MCP_SCHEMA_BUILDER .
methods GET_SESSION_MODE
redefinition .
methods HANDLE_CALL_TOOL
redefinition .
methods HANDLE_GET_PROMPT
redefinition .
methods HANDLE_INITIALIZE
redefinition .
methods HANDLE_LIST_PROMPTS
redefinition .
methods HANDLE_LIST_RESOURCES
redefinition .
methods HANDLE_LIST_RES_TMPLS
redefinition .
methods HANDLE_LIST_TOOLS
redefinition .
methods HANDLE_RESOURCES_READ
redefinition .
private section.
methods ADD_TABLE_TO_SCHEMA
importing
!NAME type STRING
!DESCRIPTION type STRING
!REQUIRED type ABAP_BOOL optional
!TYPEDESCR type ref to CL_ABAP_TYPEDESCR
changing
!SCHEMA type ref to ZCL_MCP_SCHEMA_BUILDER .
methods ADD_STRUCT_TO_SCHEMA
importing
!NAME type STRING
!DESCRIPTION type STRING
!REQUIRED type ABAP_BOOL optional
!TYPEDESCR type ref to CL_ABAP_TYPEDESCR
changing
!SCHEMA type ref to ZCL_MCP_SCHEMA_BUILDER .
methods ADD_TO_SCHEMA
importing
!NAME type STRING
!DESCRIPTION type STRING
!REQUIRED type ABAP_BOOL optional
!TYPEDESCR type ref to CL_ABAP_TYPEDESCR
changing
!SCHEMA type ref to ZCL_MCP_SCHEMA_BUILDER .
methods ADD_ELEM_TO_SCHEMA
importing
!NAME type STRING
!DESCRIPTION type STRING
!REQUIRED type ABAP_BOOL optional
!TYPEDESCR type ref to CL_ABAP_TYPEDESCR
changing
!SCHEMA type ref to ZCL_MCP_SCHEMA_BUILDER .
methods READ
importing
!REQUEST type ref to ZCL_MCP_REQ_CALL_TOOL
changing
!RESPONSE type ZIF_MCP_SERVER=>CALL_TOOL_RESPONSE .
methods SEARCH
importing
!REQUEST type ref to ZCL_MCP_REQ_CALL_TOOL
changing
!RESPONSE type ZIF_MCP_SERVER=>CALL_TOOL_RESPONSE .
methods READ_SCHEMA
returning
value(RESULT) type ref to ZCL_MCP_SCHEMA_BUILDER .
methods SEARCH_SCHEMA
returning
value(RESULT) type ref to ZCL_MCP_SCHEMA_BUILDER .
methods CREATE_DYNAMIC_STRUCTURE
importing
!COMPONENTS type ABAP_COMPONENT_TAB
returning
value(STRUCT) type ref to DATA .
ENDCLASS.
CLASS /AHRC/CL_MCP_DYNAMIC IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->ADD_ELEM_TO_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [--->] NAME TYPE STRING
* | [--->] DESCRIPTION TYPE STRING
* | [--->] REQUIRED TYPE ABAP_BOOL(optional)
* | [--->] TYPEDESCR TYPE REF TO CL_ABAP_TYPEDESCR
* | [<-->] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_elem_to_schema.
DATA:
"------------------------------------------------------------------
" Structure
"------------------------------------------------------------------
field_infos TYPE dfies,
field_description TYPE string,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
elemdescr TYPE REF TO cl_abap_elemdescr,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
field_name TYPE string.
"==================================================================
" Processing logic
"==================================================================
" Preparation
IF name IS INITIAL.
field_name = 'TABLELINE'.
ELSE.
field_name = name.
ENDIF.
" Build
elemdescr ?= typedescr.
IF elemdescr->is_ddic_type( ) EQ abap_true.
elemdescr->get_ddic_field(
RECEIVING
p_flddescr = field_infos
EXCEPTIONS
not_found = 1
no_ddic_type = 2
OTHERS = 3
).
IF sy-subrc <> 0.
MESSAGE e016(rp) WITH 'DDIC' elemdescr->absolute_name 'not found'.
RETURN.
ENDIF.
ELSE.
field_infos-inttype = elemdescr->type_kind.
ENDIF.
IF description IS INITIAL.
field_description = field_infos-scrtext_l.
ENDIF.
CASE field_infos-inttype.
WHEN cl_abap_elemdescr=>typekind_char
OR cl_abap_elemdescr=>typekind_clike
OR cl_abap_elemdescr=>typekind_csequence
OR cl_abap_elemdescr=>typekind_string.
IF field_infos-domname EQ 'BOOLE' OR
field_infos-domname EQ 'BOOLEAN' OR
field_infos-domname EQ 'ABAP_BOOL'.
schema->add_boolean( name = field_name
description = field_description
required = required ).
ELSE.
schema->add_string( name = field_name
description = field_description
* min_length =
* max_length =
required = required
).
ENDIF.
WHEN cl_abap_elemdescr=>typekind_decfloat
OR cl_abap_elemdescr=>typekind_decfloat16
OR cl_abap_elemdescr=>typekind_decfloat34
OR cl_abap_elemdescr=>typekind_float
OR cl_abap_elemdescr=>typekind_packed
OR cl_abap_elemdescr=>typekind_utclong.
schema->add_number( name = field_name
description = field_description
required = required
* minimum =
* maximum =
).
WHEN cl_abap_elemdescr=>typekind_date.
schema->add_integer( name = field_name
description = field_description
required = required
minimum = 18000101
maximum = 99991231
).
WHEN cl_abap_elemdescr=>typekind_time.
schema->add_integer( name = field_name
description = field_description
required = required
minimum = 0
maximum = 23595959
).
WHEN cl_abap_elemdescr=>typekind_numeric " ?
OR cl_abap_elemdescr=>typekind_num
OR cl_abap_elemdescr=>typekind_int
OR cl_abap_elemdescr=>typekind_int1
OR cl_abap_elemdescr=>typekind_int8
OR cl_abap_elemdescr=>typekind_int2.
schema->add_integer( name = field_name
description = field_description
required = required
* minimum =
* maximum =
).
WHEN cl_abap_elemdescr=>typekind_hex " TODO
OR cl_abap_elemdescr=>typekind_class
OR cl_abap_elemdescr=>typekind_dref
OR cl_abap_elemdescr=>typekind_intf
OR cl_abap_elemdescr=>typekind_iref
OR cl_abap_elemdescr=>typekind_oref
OR cl_abap_elemdescr=>typekind_simple
OR cl_abap_elemdescr=>typekind_struct1
OR cl_abap_elemdescr=>typekind_struct2
OR cl_abap_elemdescr=>typekind_table
OR cl_abap_elemdescr=>typekind_w
OR cl_abap_elemdescr=>typekind_xsequence
OR cl_abap_elemdescr=>typekind_xstring
OR cl_abap_elemdescr=>typekind_bref
OR cl_abap_elemdescr=>typekind_enum.
schema->add_string( name = field_name
description = |Type = { field_infos-inttype }|
* min_length =
* max_length =
required = required
).
WHEN OTHERS.
* schema->add_string( name = field_name
* description = |Type = { field_description-inttype }|
** min_length =
** max_length =
* required = required
* ).
ENDCASE.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->ADD_STRUCT_TO_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [--->] NAME TYPE STRING
* | [--->] DESCRIPTION TYPE STRING
* | [--->] REQUIRED TYPE ABAP_BOOL(optional)
* | [--->] TYPEDESCR TYPE REF TO CL_ABAP_TYPEDESCR
* | [<-->] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_struct_to_schema.
DATA:
"------------------------------------------------------------------
" Structure
"------------------------------------------------------------------
components TYPE abap_component_tab,
descr TYPE string,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
structdescr TYPE REF TO cl_abap_structdescr.
"==================================================================
" Processing logic
"==================================================================
structdescr ?= typedescr.
components = structdescr->get_components( ).
CHECK components[] IS NOT INITIAL.
IF name IS NOT INITIAL. " if name is initial -> it's an INCLUDE
schema->begin_object(
name = name
description = description
required = required
).
ENDIF.
LOOP AT components ASSIGNING FIELD-SYMBOL(<component>).
IF <component>-as_include IS INITIAL.
add_to_schema( EXPORTING name = |{ <component>-name }|
description = ''
* required =
typedescr = <component>-type
CHANGING schema = schema ).
ELSE.
add_struct_to_schema( EXPORTING name = ''
description = ''
* required =
typedescr = <component>-type
CHANGING schema = schema ).
ENDIF.
ENDLOOP.
IF name IS NOT INITIAL. " if name is initial -> it's an INCLUDE
schema->end_object( ).
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->ADD_TABLE_TO_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [--->] NAME TYPE STRING
* | [--->] DESCRIPTION TYPE STRING
* | [--->] REQUIRED TYPE ABAP_BOOL(optional)
* | [--->] TYPEDESCR TYPE REF TO CL_ABAP_TYPEDESCR
* | [<-->] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_table_to_schema.
DATA:
"------------------------------------------------------------------
" Structure
"------------------------------------------------------------------
descr TYPE string,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
datadescr TYPE REF TO cl_abap_datadescr,
tabledescr TYPE REF TO cl_abap_tabledescr.
"==================================================================
" Processing logic
"==================================================================
" Open an array
schema->begin_array(
name = name
description = description
required = required
* min_items =
* max_items =
).
" Process the content of the table: it can be
" . a simple type (tableline)
" . a structure
tabledescr ?= typedescr.
datadescr = tabledescr->get_table_line_type( ).
add_to_schema( EXPORTING name = ''
description = ''
* required =
typedescr = datadescr
CHANGING schema = schema ).
" Close the array
schema->end_array( ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->ADD_TO_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [--->] NAME TYPE STRING
* | [--->] DESCRIPTION TYPE STRING
* | [--->] REQUIRED TYPE ABAP_BOOL(optional)
* | [--->] TYPEDESCR TYPE REF TO CL_ABAP_TYPEDESCR
* | [<-->] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_to_schema.
DATA:
"------------------------------------------------------------------
" Structure
"------------------------------------------------------------------
dfies TYPE dfies,
descr TYPE string,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
elemdescr TYPE REF TO cl_abap_elemdescr.
"==================================================================
" Processing logic
"==================================================================
IF typedescr IS INSTANCE OF cl_abap_elemdescr.
add_elem_to_schema( EXPORTING name = name
description = description
required = required
typedescr = typedescr
CHANGING schema = schema ).
ELSEIF typedescr IS INSTANCE OF cl_abap_structdescr.
add_struct_to_schema( EXPORTING name = name
description = description
required = required
typedescr = typedescr
CHANGING schema = schema ).
ELSEIF typedescr IS INSTANCE OF cl_abap_tabledescr.
add_table_to_schema( EXPORTING name = name
description = description
required = required
typedescr = typedescr
CHANGING schema = schema ).
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->BUILD_DYNAMIC_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [--->] CLASS TYPE SEOCLSNAME
* | [--->] METHOD TYPE SEOCMPNAME
* | [--->] LANGUAGE TYPE SY-LANGU (default ='E')
* | [<-()] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD build_dynamic_schema.
DATA:
"----------------------------------------------------------------
" Objects
"----------------------------------------------------------------
classdescr TYPE REF TO cl_abap_classdescr,
typedescr TYPE REF TO cl_abap_typedescr,
datadescr TYPE REF TO cl_abap_datadescr,
structdescr TYPE REF TO cl_abap_structdescr,
tabledescr TYPE REF TO cl_abap_tabledescr,
elemdescr TYPE REF TO cl_abap_elemdescr,
"----------------------------------------------------------------
" Variables
"----------------------------------------------------------------
has_interface TYPE abap_bool,
clsname TYPE seocompodf-clsname,
cmpname TYPE seocompodf-cmpname.
"==================================================================
" Processing logic
"==================================================================
IF method CA '~'. " Class implements interface so use interface instead!
SPLIT method AT '~' INTO clsname
cmpname.
ELSE.
clsname = class.
cmpname = method.
ENDIF..
"==================================================================
" Processing logic
"==================================================================
TRY.
schema = NEW zcl_mcp_schema_builder( ).
* schema->add_string( name = 'parameter' required = abap_true )
* ->add_integer( name = 'count' minimum = 1 )
* ->begin_object( name = 'options' )
* ->add_boolean( name = 'flag' )
* ->end_object( ).
IF method CA '~'. " Class implements interface so use interface instead!
has_interface = abap_true.
SPLIT method AT '~' INTO clsname
cmpname.
ELSE.
clsname = class.
cmpname = method.
ENDIF.
" Descriptions
SELECT df~clsname,
df~cmpname,
df~sconame,
df~pardecltyp,
df~parpasstyp,
df~typtype,
df~type,
df~paroptionl,
tx~descript
INTO TABLE @DATA(parameter_descriptions)
FROM seosubcodf AS df LEFT OUTER JOIN seosubcotx AS tx
ON ( df~clsname EQ tx~clsname
AND df~cmpname EQ tx~cmpname
AND df~sconame EQ tx~sconame
AND tx~langu EQ @language )
WHERE df~clsname EQ @clsname
AND df~cmpname EQ @cmpname.
" Build schema
classdescr ?= cl_abap_classdescr=>describe_by_name( class ).
READ TABLE classdescr->methods ASSIGNING FIELD-SYMBOL(<method_descr>) WITH TABLE KEY name = method. " TYPE abap_methdescr
IF sy-subrc <> 0.
MESSAGE e016(rp) WITH 'Method' method 'not found'.
RETURN.
ENDIF.
LOOP AT <method_descr>-parameters ASSIGNING FIELD-SYMBOL(<parameter>) WHERE parm_kind EQ 'I'. " (I)mporting | (E)xporting | (C)hanging | (R)eturning
READ TABLE parameter_descriptions INTO DATA(parameter_description) WITH KEY sconame = <parameter>-name.
typedescr = classdescr->get_method_parameter_type(
p_method_name = method
p_parameter_name = <parameter>-name
).
add_to_schema( EXPORTING name = |{ <parameter>-name }|
description = |{ parameter_description-descript }|
required = boolc( <parameter>-is_optional EQ abap_false )
typedescr = typedescr
CHANGING schema = schema ).
ENDLOOP.
CATCH zcx_mcp_ajson_error. " JSON error
ENDTRY.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method /AHRC/CL_MCP_DYNAMIC->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.
super->constructor( ).
registered_methods = VALUE #(
( clsname = '/PLMU/CL_FRW_TEST_BO' cmpname = 'GET_FLIGHT' )
( clsname = '/AHRC/CL_DAO_EWM_MATERIAL' cmpname = '/AHRC/IF_DAO_EWM_MATERIAL~READ' )
).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->CREATE_DYNAMIC_STRUCTURE
* +-------------------------------------------------------------------------------------------------+
* | [--->] COMPONENTS TYPE ABAP_COMPONENT_TAB
* | [<-()] STRUCT TYPE REF TO DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD create_dynamic_structure.
DATA:
structdescr TYPE REF TO cl_abap_structdescr.
FIELD-SYMBOLS:
<struct> TYPE any.
"==================================================================
" Processing logic
"==================================================================
structdescr = cl_abap_structdescr=>create( p_components = components ).
CREATE DATA struct TYPE HANDLE structdescr.
* ASSIGN struct->* TO <result>.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->GET_METHOD_DETAILS
* +-------------------------------------------------------------------------------------------------+
* | [--->] CLASS TYPE SEOCLSNAME
* | [--->] METHOD TYPE SEOCMPNAME
* | [--->] LANGUAGE TYPE SY-LANGU (default ='E')
* | [<-()] METHOD_DETAILS TYPE TY_S_METHOD_DETAILS
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_method_details.
DATA:
clsname TYPE seocompodf-clsname,
cmpname TYPE seocompodf-cmpname.
"==================================================================
" Processing logic
"==================================================================
IF method CA '~'. " Class implements interface so use interface instead!
SPLIT method AT '~' INTO clsname
cmpname.
ELSE.
clsname = class.
cmpname = method.
ENDIF.
SELECT SINGLE df~clsname AS class,
df~cmpname AS method,
df~exposure AS exposure, " 0 Private, 1 Protected, 2 Public
df~mtddecltyp AS declaration_level, " 0 Instance, 1 Static method
df~mtdabstrct AS is_abstract, " 'X' abstract
tx~descript AS description
INTO @method_details
FROM seocompodf AS df LEFT OUTER JOIN seocompotx AS tx
ON ( df~clsname EQ tx~clsname
AND df~cmpname EQ tx~cmpname
AND tx~langu EQ @language )
WHERE df~clsname EQ @clsname
AND df~cmpname EQ @cmpname.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->GET_SESSION_MODE
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RESULT TYPE ZMCP_SESSION_MODE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_session_mode.
result = zcl_mcp_session=>session_mode_stateless.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_CALL_TOOL
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_CALL_TOOL
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>CALL_TOOL_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_call_tool.
DATA:
classdescr TYPE REF TO cl_abap_classdescr,
structdescr TYPE REF TO cl_abap_structdescr,
instance TYPE REF TO object,
class TYPE seoclsname,
method TYPE seocmpname,
components TYPE abap_component_tab,
components_input TYPE abap_component_tab,
components_output TYPE abap_component_tab,
signature TYPE REF TO data, " full signature of the method (IMPORTING, EXPORTING, CHANGING, RETURNING)
input TYPE REF TO data,
output TYPE REF TO data,
method_params TYPE abap_parmbind_tab.
FIELD-SYMBOLS:
<signature> TYPE any,
<input> TYPE any,
<output> TYPE any,
<field> TYPE any.
"==================================================================
" Processing logic
"==================================================================
DATA(arguments) = request->get_arguments( ).
"------------------------------------------------------------------
" Checks
"------------------------------------------------------------------
" Registration check for class, method)
SPLIT request->get_name( ) AT '|' INTO class
method.
READ TABLE registered_methods TRANSPORTING NO FIELDS WITH KEY clsname = class
cmpname = method.
IF sy-subrc <> 0.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = |Tool { request->get_name( ) } not registered.| ##NO_TEXT.
RETURN.
ENDIF.
" Class & Method check
classdescr ?= cl_abap_classdescr=>describe_by_name( class ).
READ TABLE classdescr->methods ASSIGNING FIELD-SYMBOL(<method_descr>) WITH TABLE KEY name = method. " TYPE abap_methdescr
IF sy-subrc <> 0.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_request.
response-error-message = |Tool { request->get_name( ) } cannot be called since method { method } does not exist.| ##NO_TEXT.
RETURN.
ENDIF.
"------------------------------------------------------------------
" Schema validation
"------------------------------------------------------------------
" Validate input parameter via schema validator class
TRY.
DATA(schema) = build_dynamic_schema(
class = class
method = method
).
DATA(validator) = NEW zcl_mcp_schema_validator( schema->to_json( ) ).
DATA(validation_result) = validator->validate( arguments ).
IF validation_result = abap_false.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = concat_lines_of( validator->get_errors( ) ).
RETURN.
ENDIF.
CATCH zcx_mcp_ajson_error INTO DATA(error).
response-error-code = zcl_mcp_jsonrpc=>error_codes-internal_error.
response-error-message = error->get_text( ).
RETURN.
ENDTRY.
"------------------------------------------------------------------
" Perform call
"------------------------------------------------------------------
DATA(method_details) = get_method_details( class = class method = method ).
CASE method_details-visibility.
WHEN 0 " Private
OR 1. " Protected
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_request.
response-error-message = |Tool { request->get_name( ) } cannot be called since it is not public.| ##NO_TEXT.
RETURN.
WHEN 2. " Public
" Fine!
ENDCASE.
" - - - - - - - - - - - - - - - - -
" Static VS Instance method
" - - - - - - - - - - - - - - - - -
CASE method_details-declaration_level.
WHEN 0. " Instance method
" - - - - - - - - - - - - - -
" Dynamic instantiation
" - - - - - - - - - - - - - -
IF classdescr->is_instantiatable( ) EQ abap_false.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_request.
response-error-message = |Tool { request->get_name( ) } cannot be called since constructor is not instantiable.| ##NO_TEXT.
RETURN.
ENDIF.
DATA(constructor_details) = get_method_details( class = class method = 'CONSTRUCTOR' ).
TRY.
CASE constructor_details-visibility.
WHEN 0 " Private
OR 1. " Protected
IF class CS '/AHRC/CL_D'. " Special case for HRC Software
* /ahrc/cl_class_factory=>get_class( CHANGING co_object = instance ).
CREATE OBJECT instance TYPE (class).
ELSE.
" We assume the GET_INSTANCE method always has a RETURNING parameter
READ TABLE classdescr->methods ASSIGNING FIELD-SYMBOL(<getinstance_descr>)
WITH TABLE KEY name = 'GET_INSTANCE'. " TYPE abap_methdescr
IF sy-subrc <> 0.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_request.
response-error-message = |Tool { request->get_name( ) } does not have a GET_INSTANCE method.| ##NO_TEXT.
RETURN.
ENDIF.
READ TABLE <getinstance_descr>-parameters INTO DATA(getinstance_return)
WITH KEY parm_kind = 'R'.
IF sy-subrc <> 0.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_request.
response-error-message = |Tool { request->get_name( ) } does not have a GET_INSTANCE method with a RETURNING parameter.| ##NO_TEXT.
RETURN.
ENDIF.
DATA(constructor_params) = VALUE abap_parmbind_tab(
( name = getinstance_return-name
kind = cl_abap_objectdescr=>receiving
value = REF #( instance )
)
).
CALL METHOD (class)=>('GET_INSTANCE')
PARAMETER-TABLE constructor_params.
ENDIF.
WHEN 2. " Public
CREATE OBJECT instance TYPE (class)
PARAMETER-TABLE constructor_params.
ENDCASE.
CATCH cx_sy_create_object_error INTO DATA(exception).
" Handle creation error
response-error-code = zcl_mcp_jsonrpc=>error_codes-internal_error.
response-error-message = exception->get_text( ).
RETURN.
ENDTRY.
WHEN 1. " Static method
" No trouble
ENDCASE.
" - - - - - - - - - - - - - -
" Method call
" - - - - - - - - - - - - - -
" Dynamically create a structure for the entire method signature
LOOP AT <method_descr>-parameters ASSIGNING FIELD-SYMBOL(<parameter>).
DATA(component) = VALUE abap_componentdescr(
name = <parameter>-name
type = classdescr->get_method_parameter_type(
p_method_name = method
p_parameter_name = <parameter>-name
)
).
APPEND component TO components.
IF <parameter>-parm_kind EQ cl_abap_objectdescr=>importing.
APPEND component TO components_input.
ELSE.
APPEND component TO components_output.
ENDIF.
ENDLOOP.
* signature = create_dynamic_structure( components ).
input = create_dynamic_structure( components_input ).
output = create_dynamic_structure( components_output ).
ASSIGN:
* signature->* TO <signature>,
input->* TO <input>,
output->* TO <output>.
" Prepare the dynamic call to the method
LOOP AT <method_descr>-parameters ASSIGNING <parameter>.
UNASSIGN: <field>.
CASE <parameter>-parm_kind.
WHEN cl_abap_objectdescr=>importing.
ASSIGN COMPONENT <parameter>-name OF STRUCTURE <input> TO <field>.
WHEN OTHERS.
ASSIGN COMPONENT <parameter>-name OF STRUCTURE <output> TO <field>.
ENDCASE.
INSERT VALUE #(
name = <parameter>-name
kind = COND #( WHEN <parameter>-parm_kind EQ cl_abap_objectdescr=>importing THEN cl_abap_objectdescr=>exporting
WHEN <parameter>-parm_kind EQ cl_abap_objectdescr=>exporting THEN cl_abap_objectdescr=>importing
WHEN <parameter>-parm_kind EQ cl_abap_objectdescr=>changing THEN cl_abap_objectdescr=>changing
WHEN <parameter>-parm_kind EQ cl_abap_objectdescr=>returning THEN cl_abap_objectdescr=>receiving )
value = REF #( <field> )
) INTO TABLE method_params.
ENDLOOP.
" Fill the input parameters
arguments->to_abap(
* EXPORTING iv_corresponding = abap_false
IMPORTING ev_container = <input>
).
" Dynamic method call
CASE method_details-declaration_level.
WHEN 0. " Instance method
CALL METHOD instance->(method)
PARAMETER-TABLE method_params.
WHEN 1. " Static method
CALL METHOD (class)=>(method)
PARAMETER-TABLE method_params.
ENDCASE.
"--------------------
" JSON response
"--------------------
DATA(json) = /ui2/cl_json=>serialize(
EXPORTING
data = output
compress = abap_false " Skip empty elements
).
" This should actually go to structured content (once implemented!)
" see: https://github.com/abap-ai/mcp/issues/43
response-result->add_text_content( json ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_GET_PROMPT
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_GET_PROMPT
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>GET_PROMPT_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_get_prompt.
DATA(arguments) = request->get_arguments( ).
CASE request->get_name( ).
* WHEN ``.
* response-result->set_description( `` ) ##NO_TEXT.
* response-result->add_text_message( role = zif_mcp_server=>role_user
* text = || ) ##NO_TEXT.
WHEN OTHERS.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = |Prompt { request->get_name( ) } unknown.| ##NO_TEXT.
ENDCASE.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_INITIALIZE
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_INITIALIZE
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>INITIALIZE_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_initialize.
" Capabilities
response-result->set_capabilities( VALUE #(
prompts = abap_true
resources = abap_true
tools = abap_true
) ).
" Implementation
response-result->set_implementation( VALUE #(
name = `HRC Software - Dynamic MCP Server`
version = `1.0.0`
) ).
" Instructions
response-result->set_instructions( `Use the features provided by this server only if explicitely requested. If not sure ask the user!` ) ##NO_TEXT.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_LIST_PROMPTS
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_LIST_PROMPTS
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>LIST_PROMPTS_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_list_prompts.
" In this demo instance we only have two prompts, therefore
" we do not consider cursor and max_list_results.
* response-result->set_prompts(
* VALUE #(
* ( name = `progress`
* description = `Asks the LLM to determine the progress of the Inventory campaign.`
* arguments = VALUE #( ( name = `storage_bin` description = `Storage Bin to inventoriate` required = abap_false ) )
* )
* )
* ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_LIST_RESOURCES
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_LIST_RESOURCES
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>LIST_RESOURCES_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_list_resources.
" Create static resources list
* DATA(resources) = VALUE zcl_mcp_resp_list_resources=>resources(
** ( uri = 'abap://classes/zcl_my_utility'
** name = 'My Utility Class'
** description = 'Utility class for XYZ operations'
** mime_type = 'text/x-abap' )
* ( uri = 'users/current'
* name = 'Current user profile'
* description = 'Current user profile information (especially user parameters such as Warehouse Id)'
* mime_type = 'application/json' )
* ).
* response-result->set_resources( resources ) ##NO_TEXT.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_LIST_RES_TMPLS
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_LIST_RES_TMPLS
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>LIST_RESOURCES_TMPL_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_list_res_tmpls.
" Create template list
DATA(templates) = VALUE zcl_mcp_resp_list_res_tmpl=>resource_templates(
" -- User profile
* ( uritemplate = 'users/{username}/profile'
* name = 'User Profile'
* description = 'User profile information'
* mime_type = 'application/json' )
).
" Set the templates in the response
response-result->set_resource_templates( templates ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_LIST_TOOLS
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_LIST_TOOLS
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>LIST_TOOLS_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_list_tools.
DATA:
tools TYPE zcl_mcp_resp_list_tools=>tools.
"==================================================================
" Processing logic
"==================================================================
TRY.
LOOP AT registered_methods ASSIGNING FIELD-SYMBOL(<registered_method>).
DATA(class) = <registered_method>-clsname.
DATA(method) = <registered_method>-cmpname.
" Method details
DATA(method_details) = get_method_details(
class = class
method = method
).
" Method parameters
DATA(schema) = build_dynamic_schema(
class = class
method = method
).
APPEND VALUE #( name = |{ class }{ '|' }{ method }|
description = method_details-description
input_schema = schema->to_json( ) )
TO tools ##NO_TEXT.
ENDLOOP.
CATCH zcx_mcp_ajson_error INTO DATA(error).
response-error-code = zcl_mcp_jsonrpc=>error_codes-internal_error.
response-error-message = error->get_text( ).
ENDTRY.
response-result->set_tools( tools ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->HANDLE_RESOURCES_READ
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_READ_RESOURCE
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>RESOURCES_READ_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD handle_resources_read.
TYPES:
BEGIN OF ty_s_user_parameter,
module TYPE string,
parameter_id TYPE string,
parameter_text TYPE string,
parameter_value TYPE string,
parameter_value_text TYPE string,
END OF ty_s_user_parameter,
ty_t_user_parameters TYPE STANDARD TABLE OF ty_s_user_parameter WITH NON-UNIQUE DEFAULT KEY.
DATA:
"------------------------------------------------------------------
" Tables, Structures
"------------------------------------------------------------------
ls_search_parameter TYPE /ahrc/if_dao_cmn_user_params=>ty_s_user_context_search_in,
"------------------------------------------------------------------
" Objects
"------------------------------------------------------------------
lo_dao_cmn_user_params TYPE REF TO /ahrc/if_dao_cmn_user_params,
"------------------------------------------------------------------
" Variables
"------------------------------------------------------------------
json TYPE string.
"==================================================================
" Processing logic
"==================================================================
" Check if this is a dynamic resource
DATA(uri) = request->get_uri( ).
" Extract sales order ID from dynamic resource URI
FIND '{' IN uri IN CHARACTER MODE MATCH COUNT DATA(nb_params).
IF nb_params > 0. " Dynamic resource
* CASE request->get_uri( ).
* WHEN 'read'.
* read( EXPORTING request = request CHANGING response = response ).
* ENDCASE.
ELSE. " Static resource
ENDIF.
IF json IS NOT INITIAL.
response-result->add_text_resource( uri = request->get_uri( )
mime_type = `application/json`
text = json ).
ELSE.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = |Resource { request->get_uri( ) } not found.| ##NO_TEXT.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->READ
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_CALL_TOOL
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>CALL_TOOL_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD read.
DATA:
"----------------------------------------------------------------
" Objects
"----------------------------------------------------------------
lo_dao_ewm_material TYPE REF TO /ahrc/if_dao_ewm_material.
"==================================================================
" Processing logic
"==================================================================
DATA(input) = request->get_arguments( ).
"------------------------------------------------------------------
" Schema validation
"------------------------------------------------------------------
" Validate input parameter via schema validator class
TRY.
DATA(schema) = read_schema( ).
DATA(validator) = NEW zcl_mcp_schema_validator( schema->to_json( ) ).
DATA(validation_result) = validator->validate( input ).
IF validation_result = abap_false.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = concat_lines_of( validator->get_errors( ) ).
RETURN.
ENDIF.
CATCH zcx_mcp_ajson_error INTO DATA(error).
response-error-code = zcl_mcp_jsonrpc=>error_codes-internal_error.
response-error-message = error->get_text( ).
RETURN.
ENDTRY.
"------------------------------------------------------------------
" Perform call
"------------------------------------------------------------------
" DAO Instantiation
/ahrc/cl_class_factory=>get_class( CHANGING co_object = lo_dao_ewm_material ).
IF lo_dao_ewm_material IS NOT BOUND.
DATA(lv_interface_name) = /ahrc/cl_class_factory=>get_interface_name( ir_variable = REF #( lo_dao_ewm_material ) ).
response-error-code = 'EDAO_NFD'.
MESSAGE e002(/ahrc/clmsg_util) WITH lv_interface_name INTO response-error-message.
RETURN.
ENDIF.
" Read
DATA(warehouse_id) = input->get_string( `warehouse_id` ).
DATA(material_guid) = input->get_string( `material_guid` ).
DATA(material_id) = input->get_string( `material_id` ).
lo_dao_ewm_material->read(
EXPORTING
it_material_guid = COND #( WHEN material_guid IS NOT INITIAL THEN VALUE #( ( material_guid ) ) )
it_material_id = COND #( WHEN material_id IS NOT INITIAL THEN VALUE #( ( material_id ) ) )
iv_warehouse = COND #( WHEN warehouse_id IS NOT INITIAL THEN warehouse_id )
IMPORTING
et_material = DATA(lt_materials)
es_return = DATA(ls_return)
).
IF /ahrc/cl_util_common=>message_t_check_contains_error( ls_return-return_detail ) IS NOT INITIAL.
LOOP AT ls_return-return_detail ASSIGNING FIELD-SYMBOL(<return_detail>) WHERE type CA 'EAX'.
EXIT.
ENDLOOP.
IF sy-subrc EQ 0.
response-result->add_text_content( |{ <return_detail>-message }| ) ##NO_TEXT.
ENDIF.
RETURN.
ENDIF.
" - - - - - - - - - - - - -
" Markdown response
" - - - - - - - - - - - - -
* " Create markdown table
* DATA(markdown) = |## Inventory Documents Details\n\n|.
*
* " Add table headers
* markdown = |{ markdown }\| Document Year \| Document Id \| Document Type (text) \| Category \| Reason (text) \|\n| ##NO_TEXT.
* markdown = |{ markdown }\|---------------\|-------------\|----------------------\|----------\|---------------\|\n| ##NO_TEXT.
*
* " Add table rows
* LOOP AT ls_inventory-headers ASSIGNING FIELD-SYMBOL(<header>).
* markdown = markdown &&
* |\| { <header>-document_year } \| { <header>-document_id } \| { <header>-document_type_text } \| { <header>-inventory_category } \| { <header>-reason_text } \|\n|.
* ENDLOOP.
* IF sy-subrc <> 0.
* markdown = |{ markdown }\| No Inventory document found \|\n| ##NO_TEXT.
* ENDIF.
*
* response-result->add_text_content( markdown ).
" - - - - - - - - - - - - -
" JSON response
" - - - - - - - - - - - - -
" This should actually go to structured content (once implemented!)
" see: https://github.com/abap-ai/mcp/issues/43
DATA(json) = /ui2/cl_json=>serialize(
EXPORTING
data = lt_materials
compress = abap_false " Skip empty elements
* name =
* pretty_name =
* type_descr =
).
response-result->add_text_content( json ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->READ_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RESULT TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD read_schema.
DATA(schema) = NEW zcl_mcp_schema_builder( ).
" Warehouse
schema->add_string( name = `warehouse_id`
description = `Warehouse number`
min_length = 4
max_length = 4
required = abap_true ).
" Material Id
schema->add_string( name = `material_id`
description = `Material or Product Id`
* min_length = 18
* max_length = 40
required = abap_true ).
result = schema.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->SEARCH
* +-------------------------------------------------------------------------------------------------+
* | [--->] REQUEST TYPE REF TO ZCL_MCP_REQ_CALL_TOOL
* | [<-->] RESPONSE TYPE ZIF_MCP_SERVER=>CALL_TOOL_RESPONSE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD search.
DATA:
"----------------------------------------------------------------
" Objects
"----------------------------------------------------------------
lo_dao_ewm_material TYPE REF TO /ahrc/if_dao_ewm_material,
ls_search_parameters TYPE /ahrc/if_dao_ewm_material=>ty_s_material_search_in.
"==================================================================
" Processing logic
"==================================================================
DATA(input) = request->get_arguments( ).
"------------------------------------------------------------------
" Schema validation
"------------------------------------------------------------------
" Validate input parameter via schema validator class
TRY.
DATA(schema) = search_schema( ).
DATA(validator) = NEW zcl_mcp_schema_validator( schema->to_json( ) ).
DATA(validation_result) = validator->validate( input ).
IF validation_result = abap_false.
response-error-code = zcl_mcp_jsonrpc=>error_codes-invalid_params.
response-error-message = concat_lines_of( validator->get_errors( ) ).
RETURN.
ENDIF.
CATCH zcx_mcp_ajson_error INTO DATA(error).
response-error-code = zcl_mcp_jsonrpc=>error_codes-internal_error.
response-error-message = error->get_text( ).
RETURN.
ENDTRY.
"------------------------------------------------------------------
" Perform call
"------------------------------------------------------------------
" DAO Instantiation
/ahrc/cl_class_factory=>get_class( CHANGING co_object = lo_dao_ewm_material ).
IF lo_dao_ewm_material IS NOT BOUND.
DATA(lv_interface_name) = /ahrc/cl_class_factory=>get_interface_name( ir_variable = REF #( lo_dao_ewm_material ) ).
response-error-code = 'EDAO_NFD'.
MESSAGE e002(/ahrc/clmsg_util) WITH lv_interface_name INTO response-error-message.
RETURN.
ENDIF.
" Search
DATA(warehouse_id) = input->get_string( `warehouse_id` ).
DATA(material_txt) = input->get_string( `material_txt` ).
DATA(serial_no) = input->get_string( `serial_no` ).
ls_search_parameters = VALUE #(
* warehouse = COND #( WHEN warehouse_id IS NOT INITIAL
* THEN VALUE #( ( sign = 'I' option = 'EQ' low = warehouse_id ) ) )
material_txt = COND #( WHEN material_txt IS NOT INITIAL
* THEN VALUE #( ( sign = 'I' option = 'EQ' low = material_txt ) ) )
THEN VALUE #( ( sign = 'I' option = 'CP' low = |*{ material_txt }*| ) ) ) " Contains String logic
* serial_no = COND #( WHEN serial_no IS NOT INITIAL
* THEN VALUE #( ( sign = 'I' option = 'EQ' low = serial_no ) ) )
).
lo_dao_ewm_material->search(
EXPORTING
is_search_parameter = ls_search_parameters
IMPORTING
et_materials = DATA(lt_materials)
es_return = DATA(ls_return)
).
IF /ahrc/cl_util_common=>message_t_check_contains_error( ls_return-return_detail ) IS NOT INITIAL.
LOOP AT ls_return-return_detail ASSIGNING FIELD-SYMBOL(<return_detail>) WHERE type CA 'EAX'.
EXIT.
ENDLOOP.
IF sy-subrc EQ 0.
response-result->add_text_content( |{ <return_detail>-message }| ) ##NO_TEXT.
ENDIF.
RETURN.
ENDIF.
" - - - - - - - - - - - - -
" Markdown response
" - - - - - - - - - - - - -
* " Create markdown table
* DATA(markdown) = |## Inventory Documents Details\n\n|.
*
* " Add table headers
* markdown = |{ markdown }\| Document Year \| Document Id \| Document Type (text) \| Category \| Reason (text) \|\n| ##NO_TEXT.
* markdown = |{ markdown }\|---------------\|-------------\|----------------------\|----------\|---------------\|\n| ##NO_TEXT.
*
* " Add table rows
* LOOP AT ls_inventory-headers ASSIGNING FIELD-SYMBOL(<header>).
* markdown = markdown &&
* |\| { <header>-document_year } \| { <header>-document_id } \| { <header>-document_type_text } \| { <header>-inventory_category } \| { <header>-reason_text } \|\n|.
* ENDLOOP.
* IF sy-subrc <> 0.
* markdown = |{ markdown }\| No Inventory document found \|\n| ##NO_TEXT.
* ENDIF.
*
* response-result->add_text_content( markdown ).
" - - - - - - - - - - - - -
" JSON response
" - - - - - - - - - - - - -
" This should actually go to structured content (once implemented!)
" see: https://github.com/abap-ai/mcp/issues/43
DATA(json) = /ui2/cl_json=>serialize(
EXPORTING
data = lt_materials
compress = abap_false " Skip empty elements
* name =
* pretty_name =
* type_descr =
).
response-result->add_text_content( json ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method /AHRC/CL_MCP_DYNAMIC->SEARCH_SCHEMA
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RESULT TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD search_schema.
DATA(schema) = NEW zcl_mcp_schema_builder( ).
" Warehouse (see ticket: AHRC-3878)
schema->add_string( name = `warehouse_id`
description = `Warehouse number`
min_length = 4
max_length = 4
required = abap_false ).
" Material text
schema->add_string( name = `material_txt`
description = `Description of the material or product`
required = abap_false ). ##NO_TEXT
" Serial number
schema->add_string( name = `serial_no`
description = `Serial number (only relevant if the material or product is serialized)`
required = abap_false ). ##NO_TEXT
result = schema.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method /AHRC/CL_MCP_DYNAMIC->_BUILD_DYNAMIC_SCHEMA_OLD
* +-------------------------------------------------------------------------------------------------+
* | [--->] CLASS TYPE SEOCLSNAME
* | [--->] METHOD TYPE SEOCMPNAME
* | [--->] LANGUAGE TYPE SY-LANGU (default ='E')
* | [<-()] SCHEMA TYPE REF TO ZCL_MCP_SCHEMA_BUILDER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD _BUILD_DYNAMIC_SCHEMA_OLD.
DATA:
"----------------------------------------------------------------
" Objects
"----------------------------------------------------------------
typedescr TYPE REF TO cl_abap_typedescr,
datadescr TYPE REF TO cl_abap_datadescr,
structdescr TYPE REF TO cl_abap_structdescr,
tabledescr TYPE REF TO cl_abap_tabledescr,
elemdescr TYPE REF TO cl_abap_elemdescr,
"----------------------------------------------------------------
" Variables
"----------------------------------------------------------------
clsname TYPE seocompodf-clsname,
cmpname TYPE seocompodf-cmpname.
"==================================================================
" Processing logic
"==================================================================
IF method CA '~'. " Class implements interface so use interface instead!
SPLIT method AT '~' INTO clsname
cmpname.
ELSE.
clsname = class.
cmpname = method.
ENDIF..
"==================================================================
" Processing logic
"==================================================================
TRY.
schema = NEW zcl_mcp_schema_builder( ).
* schema->add_string( name = 'parameter' required = abap_true )
* ->add_integer( name = 'count' minimum = 1 )
* ->begin_object( name = 'options' )
* ->add_boolean( name = 'flag' )
* ->end_object( ).
SELECT df~clsname,
df~cmpname,
df~sconame,
df~pardecltyp,
df~parpasstyp,
df~typtype,
df~type,
df~paroptionl,
tx~descript
INTO TABLE @DATA(parameters)
FROM seosubcodf AS df LEFT OUTER JOIN seosubcotx AS tx
ON ( df~clsname EQ tx~clsname
AND df~cmpname EQ tx~cmpname
AND df~sconame EQ tx~sconame
AND tx~langu EQ @language )
WHERE df~clsname EQ @clsname
AND df~cmpname EQ @cmpname.
CHECK sy-subrc EQ 0.
DELETE parameters WHERE pardecltyp <> 0.
LOOP AT parameters ASSIGNING FIELD-SYMBOL(<parameter>). " WHERE pardecltyp EQ 0. " 0 Importing | 1 Exporting | 2 Changing | 3 Returning
typedescr = cl_abap_typedescr=>describe_by_name( <parameter>-type ).
add_to_schema( EXPORTING name = |{ <parameter>-sconame }|
description = |{ <parameter>-descript }|
required = boolc( <parameter>-paroptionl IS NOT INITIAL )
typedescr = typedescr
CHANGING schema = schema ).
ENDLOOP.
CATCH zcx_mcp_ajson_error. " JSON error
ENDTRY.
ENDMETHOD.
ENDCLASS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment