Skip to content

Commit 201a9ba

Browse files
committed
15.00.37 - mcp printer
1 parent 7d7b9e6 commit 201a9ba

File tree

3 files changed

+209
-31
lines changed

3 files changed

+209
-31
lines changed

api_logic_server_cli/api_logic_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
1313
'''
1414

15-
__version__ = "15.00.36" # last public release: 15.00.25 (15.00.12)
15+
__version__ = "15.00.37" # last public release: 15.00.36 (15.00.12)
1616
recent_changes = \
1717
f'\n\nRecent Changes:\n' +\
18-
"\t07/02/2024 - 15.00.36: minor bug in mgr symlink creation, nw cards, mgr readme diagnostics \n"\
18+
"\t07/02/2024 - 15.00.37: minor bug in mgr symlink creation, nw cards, mgr readme diagnostics, mcp printer \n"\
1919
"\t06/30/2024 - 15.00.33: Tech Preview: genai-logic genai-add-app --vibe, bug [96, 97] \n"\
2020
"\t06/10/2024 - 15.00.12: MCP Security, win fixes for readme, graphics quotes \n"\
2121
"\t06/08/2024 - 15.00.10: MCP, optional shortening of stacktrace lines, bugfix[92] \n"\
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
2+
1. Discovered MCP servers from config file: integration/mcp/../../integration/mcp/mcp_server_discovery.json:{
3+
"servers": [
4+
{
5+
"name": "API Logic Server: basic_demo",
6+
"base_url": "http://localhost:5656",
7+
"schema_url": "http://localhost:5656/.well-known/mcp.json"
8+
}
9+
]
10+
}
11+
12+
13+
Learnings and Schema from discovery schema_url: http://localhost:5656/.well-known/mcp.json:
14+
{
15+
"base_url": "http://localhost:5656/api",
16+
"description": "API Logic Project: basic_demo",
17+
"learning": "To issue one request per row from a prior step (fan-out), use the syntax:\n\n\"$<stepIndex>[*].<fieldName>\"\n\nFor example, if step 0 returns orders, and you want to send a POST per customer:\n\nBefore (incorrect):\n \"customer_id\": \"{customer_id}\"\n\nAfter (correct):\n \"customer_id\": \"$0[*].customer_id\"\n \nOnly if 'email' is in the Natural language query, send email by issing a POST request to the SysEmail endpoint, setting the subject, message and customer_id in the body. DO NOT include a POST unless the word \u2018email\u2019 appears in the user query.\n\nFormat response as mcp_responseFormat.\n\n<mcp_responseFormat>\nclass QueryParam(BaseModel):\n name: str # attribute name\n op: str # eq, lt, gt\n val: str\n\nclass JsonValues(BaseMopdel):\n name: str # name of attribute\n value: str # value of attribute\n\nclass Resource(BaseModel):\n tool_type: str\n base_url: str\n path: str # specified use case or requirement name (use 'General' if missing)\n method: str # GET, PATCH, POST or DELETE\n body: json # data for P
18+
"resources":
19+
[
20+
{
21+
"fields": [
22+
"id",
23+
"name",
24+
"balance",
25+
"credit_limit",
26+
"email",
27+
"email_opt_out"
28+
],
29+
"filterable": [
30+
... etc
31+
32+
33+
34+
2a. LLM request:
35+
36+
Natural language query:
37+
List the orders date_shipped is null and CreatedOn before 2023-07-14, and send a discount email (subject: 'Discount Offer') to the customer for each one.
38+
39+
Learnings_and_Schema:
40+
{"http://localhost:5656/.well-known/mcp.json": {"base_url": "http://localhost:5656/api", "description": "API Logic Project: basic_demo", "learning": "To issue one request per row from a prior step (fan-out), use the syntax:\n\n\"$<stepIndex>[*].<fieldName>\"\n\nFor example, if step 0 returns orders, and you want to send a POST per customer:\n\nBefore (incorrect):\n \"customer_id\": \"{customer_id}\"\n\nAfter (correct):\n \"customer_id\": \"$0[*].customer_id\"\n \nOnly if 'email' is in the Natural language query, send email by issing a POST request to the SysEmail endpoint, setting the subject, message and customer_id in the body. DO NOT include a POST unless the word \u2018email\u2019 appears in the user query.\n\nFormat response as mcp_responseFormat.\n\n<mcp_responseFormat>\nclass QueryParam(BaseModel):\n name: str # attribute name\n op: str # eq, lt, gt\n val: str\n\nclass JsonValues(BaseMopdel):\n name: str # name of attribute\n value: str # value of attribute\n\nclass Resource(BaseModel):\n tool_type: str\n base_url: str\n path: str # specified use case or requirement name (use 'General' if missing)\n method: str # GET, PATCH, POST or
41+
... etc from step 1
42+
43+
2b. generated tool context from LLM:
44+
{
45+
"schema_version": "1.0",
46+
"resources": [
47+
{
48+
"tool_type": "json-api",
49+
"base_url": "http://localhost:5656/api",
50+
"path": "/Order",
51+
"method": "GET",
52+
"query_params": [
53+
{
54+
"name": "date_shipped",
55+
"op": "eq",
56+
"val": "null"
57+
},
58+
{
59+
"name": "CreatedOn",
60+
"op": "lt",
61+
"val": "2023-07-14"
62+
}
63+
]
64+
},
65+
{
66+
"tool_type": "json-api",
67+
"base_url": "http://localhost:5656/api",
68+
"path": "/SysEmail",
69+
"method": "POST",
70+
"body": {
71+
"subject": "Discount Offer",
72+
"message": "Dear customer, we are offering a discount on your next purchase. Please check your account for more details.",
73+
"customer_id": "$0[*].customer_id"
74+
}
75+
}
76+
]
77+
}
78+
79+
3. MCP Client Executor – Starting Tool Context Execution
80+
81+
82+
83+
➡️ MCP execute_api_step[0]:
84+
Method: GET http://localhost:5656/api/Order
85+
Query: filter=[{"name": "date_shipped", "op": "eq", "val": null}, {"name": "CreatedOn", "op": "lt", "val": "2023-07-14"}]
86+
Body: {}
87+
88+
Warning: No Flask request context available. secure API calls may not work as expected.
89+
90+
91+
➡️ MCP execute_api_step[1]:
92+
Method: POST http://localhost:5656/api/SysEmail
93+
Query: filter=[]
94+
Body: {'data': {'type': 'SysEmail', 'attributes': {'subject': 'Discount Offer', 'message': 'Dear customer, we are offering a discount on your next purchase. Please check your account for more details.', 'customer_id': 1}}}
95+
96+
Warning: No Flask request context available. secure API calls may not work as expected.
97+
98+
99+
➡️ MCP execute_api_step[1]:
100+
Method: POST http://localhost:5656/api/SysEmail
101+
Query: filter=[]
102+
Body: {'data': {'type': 'SysEmail', 'attributes': {'subject': 'Discount Offer', 'message': 'Dear customer, we are offering a discount on your next purchase. Please check your account for more details.', 'customer_id': 3}}}
103+
104+
Warning: No Flask request context available. secure API calls may not work as expected.
105+
106+
107+
➡️ MCP execute_api_step[1]:
108+
Method: POST http://localhost:5656/api/SysEmail
109+
Query: filter=[]
110+
Body: {'data': {'type': 'SysEmail', 'attributes': {'subject': 'Discount Offer', 'message': 'Dear customer, we are offering a discount on your next purchase. Please check your account for more details.', 'customer_id': 5}}}
111+
112+
Warning: No Flask request context available. secure API calls may not work as expected.
113+
114+
115+
4. MCP Client Executor – Context Results:
116+
117+
Step 0 - Results (3 rows):
118+
| CreatedOn | amount_total | customer_id | date_shipped | notes |
119+
|------------|--------------|-------------|--------------|------------------|
120+
| 2023-02-22 | 90.0 | 1 | None | Second Order |
121+
| 2023-01-22 | 220.0 | 3 | None | Pending Shipment |
122+
| 2023-01-22 | 220.0 | 5 | None | Silent Shipment |
123+
124+
Step 1 - Results (1 rows):
125+
| CreatedOn | customer_id | message | subject |
126+
|------------|-------------|--------------------------------------------------------------------------------------------------------------|----------------|
127+
| 2025-07-02 | 1 | Dear customer, we are offering a discount on your next purchase. Please check your account for more details. | Discount Offer |
128+
129+
Step 2 - Results (1 rows):
130+
| CreatedOn | customer_id | message | subject |
131+
|------------|-------------|--------------------------------------------------------------------------------------------------------------|----------------|
132+
| 2025-07-02 | 3 | Dear customer, we are offering a discount on your next purchase. Please check your account for more details. | Discount Offer |
133+
134+
Step 3 - Results (1 rows):
135+
| CreatedOn | customer_id | message | subject |
136+
|------------|-------------|--------------------------------------------------------------------------------------------------------------|----------------|
137+
| 2025-07-02 | 5 | Dear customer, we are offering a discount on your next purchase. Please check your account for more details. | Discount Offer |
138+
139+
✅ MCP Client Executor – All Steps Executed - Review Results Above
140+
.. 💡 Suggestion - Copy/Paste Response to a JsonFormatter
141+
142+
Test complete.

api_logic_server_cli/prototypes/base/integration/mcp/mcp_client_executor.py

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -224,28 +224,6 @@ def get_query_param_filter(query_params):
224224
# query_param_filter = query_param_filter.replace("date_created", 'CreatedOn') # TODO - why this name?
225225
return query_param_filter # end get_query_param_filter
226226

227-
def print_get_response(query_param_filter, mcp_response):
228-
""" Print the response from the GET request. """
229-
log.info("\n3. MCP Server (als) GET filter(query_param_filter):\n" + query_param_filter)
230-
log.info(" GET Response:\n" + mcp_response.text)
231-
results : List[Dict] = mcp_response.json()['data']
232-
# print results in a table format
233-
if results:
234-
# Get all unique keys from all result dicts
235-
keys = set()
236-
for row in results:
237-
if isinstance(row, dict):
238-
keys.update(row.keys())
239-
keys = list(keys)
240-
# Print header
241-
log.info("\n| " + " | ".join(keys) + " |")
242-
log.info("|" + "|".join(["---"] * len(keys)) + "|")
243-
# Print rows
244-
for row in results:
245-
log.info("| " + " | ".join(str(row.get(k, "")) for k in keys) + " |")
246-
else:
247-
log.info("No results found.")
248-
249227
def substitute_vars(val, context, row=None, ref_index=None):
250228
"""
251229
Substitutes variable references in a value using a provided context.
@@ -411,7 +389,7 @@ def execute_api_step(step, step_num):
411389
if has_request_context():
412390
headers = request.headers # get headers from Flask request context
413391
else:
414-
log.info("Warning: No Flask request context available. secure API calls may not work as expected.")
392+
log.info("Warning: No Flask request context available. Some API calls may not work as expected.")
415393
try:
416394
resp = requests.request(method, url, headers=headers, json=body if method in ["POST", "PATCH"] else None, params=params)
417395
resp.raise_for_status()
@@ -447,17 +425,75 @@ def execute_api_step(step, step_num):
447425
context_results.append(result)
448426
step_num += 1
449427

428+
def print_json_as_table(json_data, step_index):
429+
"""Print JSON data in table format showing only data/attributes section with column headers."""
430+
if not isinstance(json_data, dict):
431+
log.info(f"\nStep {step_index}: Non-dict result - {json_data}")
432+
return
433+
434+
# Extract data section
435+
data = json_data.get('data', [])
436+
if not data:
437+
log.info(f"\nStep {step_index}: No data found in result")
438+
return
439+
440+
# Handle both single item and list of items
441+
if isinstance(data, dict):
442+
data = [data]
443+
elif not isinstance(data, list):
444+
log.info(f"\nStep {step_index}: Data is not in expected format")
445+
return
446+
447+
# Extract attributes from all items to get all possible columns
448+
all_attributes = set()
449+
attribute_rows = []
450+
451+
for item in data:
452+
if isinstance(item, dict) and 'attributes' in item:
453+
attributes = item['attributes']
454+
all_attributes.update(attributes.keys())
455+
attribute_rows.append(attributes)
456+
else:
457+
# If no attributes section, use the item directly
458+
if isinstance(item, dict):
459+
all_attributes.update(item.keys())
460+
attribute_rows.append(item)
461+
462+
if not attribute_rows:
463+
log.info(f"\nStep {step_index}: No attributes found in data")
464+
return
465+
466+
# Sort columns for consistent display
467+
columns = sorted(list(all_attributes))
468+
469+
# Calculate column widths
470+
col_widths = {}
471+
for col in columns:
472+
col_widths[col] = max(len(str(col)),
473+
max(len(str(row.get(col, ""))) for row in attribute_rows))
474+
475+
# Print table header
476+
log.info(f"\nStep {step_index} - Results ({len(attribute_rows)} rows):")
477+
header = "| " + " | ".join(col.ljust(col_widths[col]) for col in columns) + " |"
478+
separator = "|" + "|".join("-" * (col_widths[col] + 2) for col in columns) + "|"
479+
480+
log.info(header)
481+
log.info(separator)
482+
483+
# Print table rows
484+
for row in attribute_rows:
485+
row_str = "| " + " | ".join(str(row.get(col, "")).ljust(col_widths[col]) for col in columns) + " |"
486+
log.info(row_str)
487+
450488
if print := True: # print context (which is just the GETs)
451489
log.info("\n\n4. MCP Client Executor – Context Results:")
452490
for each_context_result in context_results:
453-
print_print = each_context_result
454-
if isinstance(each_context_result, dict):
455-
each_print = json.dumps(each_context_result, indent=4)
456-
print_line = f"\nStep {context_results.index(each_context_result)}\n{each_print}"
457-
log.info(print_line)
491+
step_index = context_results.index(each_context_result)
492+
print_json_as_table(each_context_result, step_index)
458493
pass
459494

460-
log.info("\n✅ MCP Client Executor – All Steps Executed\n")
495+
log.info("\n✅ MCP Client Executor – All Steps Executed - Review Results Above")
496+
log.info(".. 💡 Suggestion - Copy/Paste Response to a JsonFormatter\n")
461497

462498
return context_results
463499

0 commit comments

Comments
 (0)