ApiLogicServer 14.5.4__py3-none-any.whl → 14.5.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. api_logic_server_cli/api_logic_server.py +4 -122
  2. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  3. api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
  4. api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
  5. api_logic_server_cli/create_from_model/api_logic_server_utils.py +29 -10
  6. api_logic_server_cli/create_from_model/dbml.py +15 -12
  7. api_logic_server_cli/database/basic_demo.sqlite +0 -0
  8. api_logic_server_cli/genai/genai.py +4 -0
  9. api_logic_server_cli/genai/genai_svcs.py +14 -3
  10. api_logic_server_cli/manager.py +3 -1
  11. api_logic_server_cli/prototypes/base/.vscode/launch.json +40 -0
  12. api_logic_server_cli/prototypes/base/api/api_discovery/mcp_discovery.py +58 -0
  13. api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.py +68 -2
  14. api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.pyZ +73 -0
  15. api_logic_server_cli/prototypes/{basic_demo/customizations/integration → base/integration/mcp}/.DS_Store +0 -0
  16. api_logic_server_cli/prototypes/base/integration/mcp/README_mcp.md +15 -0
  17. api_logic_server_cli/prototypes/{basic_demo/customizations → base}/integration/mcp/mcp_client_executor.py +91 -103
  18. api_logic_server_cli/prototypes/base/integration/mcp/mcp_schema.txt +47 -0
  19. api_logic_server_cli/prototypes/base/integration/mcp/mcp_server_discovery.json +9 -0
  20. api_logic_server_cli/prototypes/base/integration/mcp/test_notes.txt +37 -0
  21. api_logic_server_cli/prototypes/basic_demo/README.md +251 -91
  22. api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_discovery.py +1 -1
  23. api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
  24. api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +27 -12
  25. api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +1 -1
  26. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/Zmcp_client_executor.py +294 -0
  27. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_tool_context.json +25 -0
  28. api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/test_notes.txt +37 -0
  29. api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +1 -20
  30. api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/email_request.py +47 -0
  31. api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/mcp_client_executor_request.py +320 -0
  32. api_logic_server_cli/prototypes/basic_demo/customizations/logic/logic_discovery/simple_constraints.py +25 -0
  33. api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +39 -32
  34. api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
  35. api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +39 -44
  36. api_logic_server_cli/prototypes/manager/.vscode/launch.json +21 -0
  37. api_logic_server_cli/prototypes/manager/{README.md → READMEz.md} +4 -4
  38. api_logic_server_cli/prototypes/manager/REAMDE.md +1057 -0
  39. api_logic_server_cli/prototypes/manager/system/genai/mcp_learning/mcp.prompt +27 -0
  40. api_logic_server_cli/prototypes/manager/system/install-ApiLogicServer-dev/install-ApiLogicServer-dev.sh +2 -1
  41. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/METADATA +1 -1
  42. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/RECORD +46 -42
  43. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/WHEEL +1 -1
  44. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/3_executor_test_agent.py +0 -52
  45. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +0 -201
  46. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +0 -17
  47. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +0 -1731
  48. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +0 -5
  49. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +0 -1731
  50. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +0 -1782
  51. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +0 -264
  52. api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +0 -1782
  53. api_logic_server_cli/prototypes/genai_demo/logic/logic_discovery/auto_discovery.py +0 -52
  54. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/entry_points.txt +0 -0
  55. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/licenses/LICENSE +0 -0
  56. {apilogicserver-14.5.4.dist-info → apilogicserver-14.5.15.dist-info}/top_level.txt +0 -0
@@ -12,10 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
12
12
  Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
13
13
  '''
14
14
 
15
- __version__ = "14.05.04" # last public release: 14.05.00
15
+ __version__ = "14.05.15" # last public release: 14.05.00
16
16
  recent_changes = \
17
17
  f'\n\nRecent Changes:\n' +\
18
- "\t05/20/2024 - 14.05.04: factored mcp filters with working date range (AND), email stub, use basic_demo custs for genai_demo \n"\
18
+ "\t05/28/2024 - 14.05.15: tour - re-factored default mcp req with generic Post, email stub, use basic_demo custs for genai_demo \n"\
19
19
  "\t05/16/2024 - 14.05.00: safrs 3.1.7, running mcp preview \n"\
20
20
  "\t04/27/2024 - 14.04.00: Graphics preview, Vibe install fix, Improved IDE Chat Logic, MCP Exploration \n"\
21
21
  "\t03/30/2024 - 14.03.25: WebGenAI fixes for Kafka and Keycloak \n"\
@@ -100,7 +100,7 @@ if debug_value is not None:
100
100
  logging.getLogger('create_from_model.model_creation_services').setLevel(logging.DEBUG)
101
101
 
102
102
 
103
- log.debug("Patch to enable import of outer directories")
103
+ # log.debug("Patch to enable import of outer directories")
104
104
  sys.path.append(get_api_logic_server_dir()) # e.g, on Docker: export PATH="/home/api_logic_server/api_logic_server_cli"
105
105
  api_logic_server_path = os.path.dirname(get_api_logic_server_dir()) # e.g: export PATH="/home/api_logic_server"
106
106
  sys.path.append(api_logic_server_path)
@@ -1341,7 +1341,7 @@ from database import <project.bind_key>_models
1341
1341
 
1342
1342
  log.debug("\n==================================================================")
1343
1343
  if msg != "":
1344
- log.debug(" 2. Add User.Login endpoint")
1344
+ log.debug(" 2. Add User.Login endpoint for Swagger")
1345
1345
  log.debug("==================================================================\n")
1346
1346
  login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint.txt")}'
1347
1347
  auth_models_file_name = f'{self.project_directory_path.joinpath("database/database_discovery/authentication_models.py")}'
@@ -1380,124 +1380,6 @@ from database import <project.bind_key>_models
1380
1380
  self.model_creation_services.resource_list = save_resource_list
1381
1381
 
1382
1382
 
1383
- def add_auth_from_scratch(self, msg: str, is_nw: bool = False): # TODO: remove
1384
- """add authentication models to project, update config; leverage multi-db support. kat
1385
-
1386
- Prior to 10.04.50, if provider_type is sql:
1387
- 1. add-db --auth-db_url= [ auth | db_url ] by calling self.create_project()
1388
- 2. add user.login endpoint
1389
- 3. Set SECURITY_ENABLED in config.py
1390
- 4. Adding Sample authorization to security/declare_security.py, or user
1391
-
1392
- Alert: complicated self.create_project() non-recursive flow:
1393
- 1. Use model_creation_services to create auth models
1394
- 2. By re-running app_creator (which leaves resources at auth, not db)
1395
- 3. So, save/restore resource_list, bind_key, db_url, abs_db_url
1396
-
1397
- Args:
1398
- msg (str): eg: "ApiLogicProject customizable project created. Adding Security:")
1399
- is_nw (bool): is northwind, which means we add the nw security logic
1400
- provider_type (str): sql or keycloak
1401
- """
1402
-
1403
- if create_utils.does_file_contain(search_for="SECURITY_ENABLED = True #",
1404
- in_file=f'{self.project_directory}/config/config.py'):
1405
- if create_utils.does_file_contain(search_for="sql.auth_provider import Authentication_Provider",
1406
- in_file=f'{self.project_directory}/config/config.py'):
1407
- pass # ok to go from sql -> keycloak
1408
- else:
1409
- log.info(f'\nAlready present in config/config.py - no action taken\n')
1410
- return
1411
-
1412
- if self.auth_provider_type == 'keycloak':
1413
- log.debug("\n==================================================================")
1414
- if msg != "":
1415
- log.debug(" 1. Set SECURITY_ENABLED in config.py")
1416
- log.debug("==================================================================\n")
1417
- create_utils.replace_string_in_file(search_for="SECURITY_ENABLED = False #",
1418
- replace_with='SECURITY_ENABLED = True #',
1419
- in_file=f'{self.project_directory}/config/config.py')
1420
- create_utils.replace_string_in_file(search_for="security.authentication_provider.sql.auth_provider",
1421
- replace_with='security.authentication_provider.keycloak.auth_provider',
1422
- in_file=f'{self.project_directory}/config/config.py')
1423
- create_utils.copy_md(project = self, from_doc_file = "Keycloak-devnotes.md",
1424
- to_project_file='devops/keycloak/Readme-keycloak.md')
1425
- else:
1426
- save_run = self.run
1427
- save_command = self.command
1428
- save_db_url = self.db_url
1429
- save_abs_db_url = self.abs_db_url
1430
- save_nw_db_status = self.nw_db_status
1431
- save_resource_list = None
1432
- if self.model_creation_services is not None:
1433
- save_resource_list = self.model_creation_services.resource_list
1434
- save_bind_key = self.bind_key
1435
- self.command = "add_db"
1436
- if self.auth_db_url == "":
1437
- self.auth_db_url = 'auth' # for create manager
1438
- self.db_url = self.auth_db_url
1439
- self.bind_key = "authentication"
1440
- is_northwind = is_nw or self.nw_db_status in ["nw", "nw+"] # nw_db_status altered in create_project
1441
- if is_northwind: # is_nw or self.nw_db_status == "nw":
1442
- self.db_url = "auth" # shorthand for api_logic_server_cli/database/auth...
1443
-
1444
- ''' non-recursive call to create_project() '''
1445
- self.run = False
1446
- self.create_project() # not creating project, but using model creation svcs to add authdb
1447
-
1448
- self.run = save_run
1449
- self.command = save_command
1450
- self.db_url = save_db_url
1451
- self.abs_db_url = save_abs_db_url
1452
- self.bind_key = save_bind_key
1453
- self.nw_db_status = save_nw_db_status
1454
- self.model_creation_services.resource_list = save_resource_list
1455
-
1456
- log.debug("\n==================================================================")
1457
- if msg != "":
1458
- log.debug(" 2. Add User.Login endpoint")
1459
- log.debug("==================================================================\n")
1460
- login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint.txt")}'
1461
- auth_models_file_name = f'{self.project_directory_path.joinpath("database/authentication_models.py")}'
1462
- with open(login_endpoint_filename, 'r') as file:
1463
- login_endpoint_data = file.read()
1464
- create_utils.insert_lines_at(lines=login_endpoint_data,
1465
- at='UserRoleList : Mapped[List["UserRole"]] = relationship(back_populates="user")',
1466
- after=True,
1467
- file_name=auth_models_file_name)
1468
- login_endpoint_filename = f'{self.api_logic_server_dir_path.joinpath("templates/login_endpoint_imports.txt")}'
1469
- auth_models_file_name = f'{self.project_directory_path.joinpath("database/authentication_models.py")}'
1470
- with open(login_endpoint_filename, 'r') as file:
1471
- login_endpoint_data = file.read()
1472
- create_utils.insert_lines_at(lines=login_endpoint_data,
1473
- at="import declarative_base", after=True,
1474
- file_name=auth_models_file_name)
1475
-
1476
- log.debug("\n==================================================================")
1477
- if msg != "":
1478
- log.debug(" 3. Set SECURITY_ENABLED in config.py")
1479
- log.debug("==================================================================\n")
1480
- create_utils.replace_string_in_file(search_for="SECURITY_ENABLED = False #",
1481
- replace_with='SECURITY_ENABLED = True #',
1482
- in_file=f'{self.project_directory}/config/config.py')
1483
- if is_northwind: # is_nw or self.nw_db_status == "nw":
1484
- log.debug("\n==================================================================")
1485
- if msg != "":
1486
- if msg != "":
1487
- log.debug(" 4. Adding Sample authorization to security/declare_security.py")
1488
- log.debug("==================================================================\n\n")
1489
- nw_declare_security_py_path = self.api_logic_server_dir_path.\
1490
- joinpath('prototypes/nw/security/declare_security.py')
1491
- declare_security_py_path = self.project_directory_path.joinpath('security/declare_security.py')
1492
- shutil.copyfile(nw_declare_security_py_path, declare_security_py_path)
1493
- else:
1494
- log.debug("\n==================================================================")
1495
- if msg != "":
1496
- log.debug(" 4. TODO: Declare authorization in security/declare_security.py")
1497
- log.debug("==================================================================\n\n")
1498
- self.add_auth_in_progress = False
1499
-
1500
-
1501
1383
  def insert_tutorial_into_readme(self):
1502
1384
  """ insert docs tutorial.md into readme at --> Tip: create the sample """
1503
1385
  project_readme_file_path = self.project_directory_path.joinpath('readme.md')
@@ -1,3 +1,3 @@
1
- last_created_date: May 20, 2025 15:00:15
2
- last_created_project_name: ../../../servers/basic_demo
3
- last_created_version: 14.05.03
1
+ last_created_date: May 27, 2025 20:52:10
2
+ last_created_project_name: samples/nw_sample_nocust
3
+ last_created_version: 14.05.12
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ import re
3
4
  import shutil
4
5
  import subprocess, os, sys
5
6
  from pathlib import Path
@@ -78,14 +79,14 @@ def get_project_directory_and_api_name(project):
78
79
  rtn_api_name, \
79
80
  rtn_merge_into_prototype
80
81
 
81
- def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "README.md"):
82
+ def copy_md(project, from_doc_file: str, to_project_file: str = "README.md"):
82
83
  """ Copy readme files (and remove !!!) from:
83
84
 
84
85
  1. github (to acquire more recent version since release)
85
86
 
86
87
  2. dev docs, if exists (gold version in docs, not prototypes).
87
88
 
88
- Used by Sample-AI; Sample-Integration (nw-), Tutorial, Tutorial-3 (3 projects), Sample-Basic-Demo;
89
+ Used by Sample-AI; Sample-Integration (nw-), Tutorial, Tutorial-3 (3 projects), Sample-Basic-Demo; Manager
89
90
 
90
91
  Removing !!! -- special handling
91
92
 
@@ -95,14 +96,18 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
95
96
 
96
97
  Image references are made absolute (to github).
97
98
 
98
- Doc Links are not well displayed in Codespaces, so should me minimized.
99
+ Doc Links are made absolute.
99
100
 
100
101
  Args:
101
- project (ProjectRun): project object (project name, etc)
102
- from_doc_file (str): eg, Sample-Basic_Demo.md
103
- to_project_file (str, optional): _description_. Defaults to "README.md".
102
+ project (ProjectRun or Path): project object (project name, etc)
103
+ from_doc_file (str): eg, Sample-Basic_Demo.md (no docs/)
104
+ to_project_file (str, optional): location of target. Defaults to "README.md".
104
105
  """
105
- project_path = project.project_directory_path
106
+ if isinstance(project, Path):
107
+ project_path = project
108
+ else:
109
+ project_path = project.project_directory_path
110
+
106
111
  to_file = project_path.joinpath(to_project_file)
107
112
  docs_path = Path(get_api_logic_server_dir()).parent.parent
108
113
  from_doc_file_path = docs_path.joinpath(f'Docs/docs/{from_doc_file}')
@@ -126,7 +131,10 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
126
131
  copyfile(src = from_doc_file_path, dst = to_file)
127
132
 
128
133
  # now remove the !!, and unindent (mkdocs features fail in a readme)
129
- if to_file.exists():
134
+ if not to_file.exists(): # can occur if offline
135
+ shutil.copy(Path(get_api_logic_server_dir()).joinpath('prototypes/base').joinpath('readme.md'),
136
+ to_file)
137
+ else:
130
138
  with open(str(to_file), "r") as readme_file:
131
139
  readme_lines_mkdocs = readme_file.readlines()
132
140
  readme_lines_md = []
@@ -134,7 +142,7 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
134
142
  db_line_num = 0
135
143
  for each_line in readme_lines_mkdocs:
136
144
  db_line_num += 1
137
- if "ai-driven-automation-video" in each_line:
145
+ if "title: Instant Microservices" in each_line:
138
146
  debug_str = "Good Breakpoint"
139
147
  if "from docsite" in each_line:
140
148
  each_line = each_line.replace("from docsite", "from docsite, for readme")
@@ -160,14 +168,25 @@ def copy_md(project: 'ProjectRun', from_doc_file: str, to_project_file: str = "R
160
168
  each_line = each_line[4:]
161
169
  in_mkdocs_block_with_sections = True
162
170
  each_line = each_line.replace('{:target="_blank" rel="noopener"}', '')
163
- if each_line.startswith('![') or each_line.startswith('[!['):
171
+ if each_line.startswith('![') or each_line.startswith('[![') or each_line.startswith(' !['):
164
172
  if "https://github.com/ApiLogicServer" not in each_line: # make doc-relative urls absolute...
165
173
  if "creates-and-runs-video" in each_line:
166
174
  debug_stop = "good stop"
167
175
  each_line = each_line.replace('images', 'https://github.com/ApiLogicServer/Docs/blob/main/docs/images')
168
176
  each_line = each_line.replace('png)', 'png?raw=true)')
177
+ each_line = each_line.replace('jpeg)', 'jpeg?raw=true)')
178
+ each_line = each_line.replace('jpg)', 'jpg?raw=true)')
169
179
  else:
170
180
  pass # image is absolute - don't alter
181
+ if '.md' in each_line:
182
+ # replace (<name>.md) with (https://apilogicserver.github.io/Docs/<name>)
183
+ each_line = re.sub(
184
+ r'\(([^)]+\.md)\)',
185
+ r'(https://apilogicserver.github.io/Docs/\1)',
186
+ each_line
187
+ )
188
+ each_line = each_line.replace('.md', '')
189
+ pass
171
190
  readme_lines_md.append(each_line)
172
191
  with open(str(to_file), "w") as readme_file:
173
192
  readme_file.writelines(readme_lines_md)
@@ -7,15 +7,12 @@ import os
7
7
  import pathlib
8
8
  from pathlib import Path
9
9
  from typing import NewType, List, Tuple, Dict
10
-
11
- import sqlalchemy
12
- import yaml
13
10
  from sqlalchemy import MetaData, false
14
11
  import datetime
15
12
  import api_logic_server_cli.create_from_model.model_creation_services as create_from_model
16
13
  import api_logic_server_cli.create_from_model.api_logic_server_utils as create_utils
17
14
  from dotmap import DotMap
18
-
15
+ import api_logic_server_cli.genai.genai_svcs as genai_svcs
19
16
  from api_logic_server_cli.create_from_model.meta_model import Resource, ResourceAttribute, ResourceRelationship
20
17
 
21
18
 
@@ -74,9 +71,6 @@ class DBMLCreator(object):
74
71
  "tool_type": "json-api",
75
72
  "schema_version": "1.0",
76
73
  "base_url": "http://localhost:5656/api",
77
- "query_params": "- JSON:API custom filtering using a filter array (e.g., filter=[{\"name\":\"date_shipped\",\"op\":\"gt\",\"val\":\"2023-07-14\"}])",
78
- "expected_response": "Respond with a JSON object with tool_type, schema_version, base_url, and an array of resources including: path, method, query_params array or body, headers, expected_output.",
79
- "email_services": "iff email is requested, Send email by issing a POST request to the Email endpoint, setting the customer_id and message in the body.",
80
74
  "description": f"API Logic Project: {self.mod_gen.project.project_name_last_node}",
81
75
  "resources": [
82
76
  ]
@@ -198,7 +192,7 @@ class DBMLCreator(object):
198
192
 
199
193
 
200
194
 
201
- def create_mcp_json_file(self):
195
+ def create_docs_mcp(self):
202
196
  """ create docs/mcp_schema.json - create self.mcp_schema['resources'] entries like:
203
197
 
204
198
  '''
@@ -213,6 +207,17 @@ class DBMLCreator(object):
213
207
  '''
214
208
  """
215
209
 
210
+ docs_path = Path(self.mod_gen.project_directory).joinpath('docs')
211
+ docs_path.mkdir(parents=True, exist_ok=True)
212
+ mcp_learning_src = genai_svcs.get_manager_path(use_env=True).joinpath('system/genai/mcp_learning')
213
+ mcp_learning_dst = Path(self.mod_gen.project_directory).joinpath('docs/mcp_learning')
214
+ if mcp_learning_dst.exists():
215
+ shutil.rmtree(mcp_learning_dst)
216
+ shutil.copytree(
217
+ src=mcp_learning_src,
218
+ dst=mcp_learning_dst
219
+ )
220
+
216
221
  resources : list = self.mcp_schema['resources']
217
222
  for each_resource_name in self.mod_gen.resource_list:
218
223
  if self.do_process_resource(each_resource_name):
@@ -226,9 +231,7 @@ class DBMLCreator(object):
226
231
  each_inserted_resource['fields'].append(each_attr.name)
227
232
  each_inserted_resource['filterable'] = each_inserted_resource['fields']
228
233
  resources.append(each_inserted_resource)
229
- docs_path = Path(self.mod_gen.project_directory).joinpath('docs')
230
- docs_path.mkdir(parents=True, exist_ok=True)
231
- mcp_schema_path = Path(self.mod_gen.project_directory).joinpath('docs/mcp_schema.json')
234
+ mcp_schema_path = Path(self.mod_gen.project_directory).joinpath('docs/mcp_learning/mcp_schema.json')
232
235
  # write self.mcp_schema dict to json file
233
236
  with open(mcp_schema_path, 'w') as f:
234
237
  json.dump(self.mcp_schema, f, indent=4)
@@ -240,5 +243,5 @@ def create(model_creation_services: create_from_model.ModelCreationServices):
240
243
  """
241
244
  dbml_creator = DBMLCreator(model_creation_services)
242
245
  dbml_creator.create_docs_dbml_file()
243
- dbml_creator.create_mcp_json_file()
246
+ dbml_creator.create_docs_mcp()
244
247
 
@@ -587,12 +587,16 @@ class GenAI(object):
587
587
  shutil.copyfile(self.project.genai_repaired_response, response_file)
588
588
  is_genai_demo = False
589
589
  if os.getenv('APILOGICPROJECT_IS_GENAI_DEMO') is not None or self.project.project_name == 'genai_demo':
590
+ # fail safe demo - be sure AI does not fail, and that the data model names are predictable for add-cust
590
591
  self.project.project_directory_path.joinpath('docs/project_is_genai_demo.txt').touch()
591
592
  # and DON'T create test data (db.sqlite already set up in recursive copy)
592
593
  project_docs_response = self.project.project_directory_path.joinpath('docs/response.json')
593
594
  with open(project_docs_response, "w") as response_file: # WebG uses this for wg_rules
594
595
  json.dump(self.response_dict, response_file, indent=4)
595
596
  pass # not possible on create_db_models, since project paths not yet set by api_logic_server
597
+ if add_mcp := False: # bad idea - fails with Ont seeking email (which would be confusing)
598
+ from api_logic_server_cli.add_cust import add_cust
599
+ add_cust.add_basic_demo_customizations(project=self.project)
596
600
 
597
601
  else: # normal path
598
602
  genai_svcs.rebuild_test_data_for_project(
@@ -924,14 +924,22 @@ def call_chatgpt(messages: List[Dict[str, str]], api_version: str, using: str) -
924
924
  log.error(f"\n\nError: ChatGPT call failed\n- please see https://apilogicserver.github.io/Docs/WebGenAI-CLI/#configuratio\n{inst}\n\n")
925
925
  sys.exit(1)
926
926
 
927
- def get_manager_path() -> Path:
927
+ def get_manager_path(use_env: bool = False) -> Path:
928
928
  """ Checks cwd, parent, and grandparent for system/genai
929
929
 
930
930
  * Possibly could add cli arg later
931
931
 
932
+ Args:
933
+ use_env: bool if cannot find manager from project, use install location
934
+
932
935
  Returns:
933
936
  Path: Manager path (contains system/genai)
934
937
  """
938
+
939
+ if use_env and os.getenv("APILOGICSERVER_HOME") is not None:
940
+ result_path = Path(os.getenv("APILOGICSERVER_HOME")).joinpath('api_logic_server_cli/prototypes/manager')
941
+ return result_path
942
+
935
943
  result_path = Path(os.getcwd()) # normal case - project at manager root
936
944
  check_system_genai = result_path.joinpath('system/genai')
937
945
  if check_system_genai.exists():
@@ -964,8 +972,11 @@ def get_manager_path() -> Path:
964
972
 
965
973
  result_path = result_path.parent # try ancestors - this is for import testing
966
974
  check_system_genai = result_path.joinpath('system/genai')
967
- assert check_system_genai.exists(), f"Manager Directory not found: {check_system_genai}"
968
-
975
+
976
+ if not check_system_genai.exists():
977
+ if use_env:
978
+ result_path = Path(create_utils.get_api_logic_server_dir()).joinpath('prototypes/manager/system/genai/mcp_learning')
979
+ check_system_genai.exists(), f"Manager Directory not found and APILOGICSERVER_HOME not set: {check_system_genai}"
969
980
  return result_path
970
981
 
971
982
 
@@ -117,7 +117,8 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
117
117
  from_docker_dir = api_logic_server_path.joinpath('prototypes/manager_docker')
118
118
  copied_path = shutil.copytree(src=from_docker_dir, dst=to_dir, dirs_exist_ok=True)
119
119
 
120
- file_src = f"https://raw.githubusercontent.com/ApiLogicServer/ApiLogicServer-src/main/api_logic_server_cli//README.md"
120
+ # get latest readme from git (eg, has been updated since pip install)
121
+ file_src = f"https://raw.githubusercontent.com/ApiLogicServer/ApiLogicServer-src/main/api_logic_server_cli/prototypes/manager/README.md"
121
122
  readme_path = to_dir.joinpath('README.md')
122
123
  try:
123
124
  r = requests.get(file_src) # , params=params)
@@ -130,6 +131,7 @@ def create_manager(clean: bool, open_with: str, api_logic_server_path: Path,
130
131
  pass # just fall back to using the pip-installed version
131
132
  except: # do NOT fail
132
133
  pass # just fall back to using the pip-installed version
134
+ create_utils.copy_md(from_doc_file='Sample-Basic-Tour.md', project = to_dir)
133
135
 
134
136
  if not samples:
135
137
  shutil.rmtree(to_dir.joinpath(f'{docker_volume}system/app_model_editor'))
@@ -107,6 +107,32 @@
107
107
  "console": "internalConsole",
108
108
  "internalConsoleOptions": "openOnSessionStart"
109
109
  },
110
+ {
111
+ "name": " - Run designated Python file",
112
+ // use this for test programs, eg to test the server
113
+ "type": "debugpy",
114
+ "request": "launch",
115
+ "cwd": "${workspaceFolder}",
116
+ "program": "${input:inputProjectName}",
117
+ "redirectOutput": true,
118
+ "justMyCode": false,
119
+ "args": ["go"],
120
+ "console": "internalConsole",
121
+ "internalConsoleOptions": "openOnSessionStart"
122
+ },
123
+ {
124
+ "name": "MCP - Model Context Protocol - Client Executor",
125
+ // a simple client to test the Model Context Protocol, default request for basic_demo
126
+ "type": "debugpy",
127
+ "request": "launch",
128
+ "cwd": "${workspaceFolder}",
129
+ "program": "integration/mcp/mcp_client_executor.py",
130
+ "redirectOutput": true,
131
+ "justMyCode": false,
132
+ "args": ["list customers with balance over 100"],
133
+ "console": "internalConsole",
134
+ "internalConsoleOptions": "openOnSessionStart"
135
+ },
110
136
  {
111
137
  "name": "Install Ontimize (npm install)",
112
138
  "type": "node",
@@ -270,5 +296,19 @@
270
296
  "justMyCode": false,
271
297
  "console": "integratedTerminal"
272
298
  }
299
+ ],
300
+ "inputs": [
301
+ {
302
+ "id": "inputProjectName",
303
+ "type": "promptString",
304
+ "description": "Python File Name to Run",
305
+ "default": "integration/mcp/mcp_client_executor.py"
306
+ },
307
+ {
308
+ "id": "inputMcpRequest",
309
+ "type": "promptString",
310
+ "description": "Request for MCP Client Executor",
311
+ "default": "find customers with balance over 500"
312
+ }
273
313
  ]
274
314
  }
@@ -0,0 +1,58 @@
1
+ from flask import request, jsonify
2
+ from flask import Flask, redirect, send_from_directory, send_file
3
+ import logging
4
+ import os
5
+ import json
6
+ import io
7
+
8
+ import requests
9
+ from config.config import Args # circular import error if at top
10
+
11
+ app_logger = logging.getLogger("api_logic_server_app")
12
+
13
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
14
+ pass
15
+
16
+ def get_server_url():
17
+ """ return the server URL for the OpenAPI spec """
18
+ result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
19
+ # get env variable API_LOGIC_SERVER_TUNNEL (or None)
20
+ if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
21
+ app_logger.info(f".. tunnel URL: {tunnel_url}")
22
+ result = tunnel_url
23
+ return result # + '/api'
24
+
25
+
26
+
27
+ @app.route('/.well-known/mcp.json', methods=['GET'])
28
+ def mcp_discovery(path=None):
29
+ ''' called by mcp_client_executor for discovery, eg:
30
+ ```
31
+ {
32
+ "tool_type": "json-api",
33
+ "schema_version": "1.0",
34
+ "base_url": "https://crm.company.com",
35
+ "resources": [
36
+ {
37
+ "name": "Customer",
38
+ "path": "/Customer",
39
+ "methods": ["GET", "PATCH"],
40
+ "fields": ["id", "name", "balance", "credit_limit"],
41
+ "filterable": ["name", "credit_limit"],
42
+ "example": "List customers with credit over 5000"
43
+ }
44
+ ]
45
+ }
46
+ ```
47
+ test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
48
+ '''
49
+ # return docs/mcp_schema.json
50
+ schema_path = os.path.join(project_dir, "docs/mcp_learning/mcp_schema.json")
51
+ try:
52
+ with open(schema_path, "r") as schema_file:
53
+ schema = json.load(schema_file)
54
+ return jsonify(schema), 200
55
+ except Exception as e:
56
+ app_logger.error(f"Error loading MCP schema: {e}")
57
+ return jsonify({"error": "MCP schema not found"}), 404
58
+ pass
@@ -1,11 +1,15 @@
1
+ import json
1
2
  from sqlalchemy.ext.declarative import declarative_base
2
3
  from sqlalchemy import Column, DECIMAL, Date, ForeignKey, Integer, String
3
- from safrs import SAFRSBase
4
+ from safrs import SAFRSBase, ValidationError
4
5
  from flask_login import UserMixin
5
6
  import safrs, flask_sqlalchemy
6
7
  from safrs import jsonapi_attr
7
8
  from flask_sqlalchemy import SQLAlchemy
8
9
  from datetime import datetime
10
+ import operator
11
+ import json
12
+
9
13
 
10
14
  Base = declarative_base() # type: flask_sqlalchemy.model.DefaultMeta
11
15
  #vh new x
@@ -28,7 +32,7 @@ def jsonapi_filter(cls):
28
32
  if sqlWhere != "":
29
33
  return query.filter(text(sqlWhere))
30
34
  else:
31
- return query.filter(or_(*expressions))
35
+ return query.filter(and_(*expressions))
32
36
 
33
37
  class SAFRSBaseX(SAFRSBase, safrs.DB.Model):
34
38
  __abstract__ = True
@@ -58,6 +62,68 @@ class SAFRSBaseX(SAFRSBase, safrs.DB.Model):
58
62
 
59
63
  return super()._s_parse_attr_value(attr_name, attr_val)
60
64
 
65
+
66
+ @classmethod
67
+ def _s_filter(cls, *filter_args, **filter_kwargs):
68
+ """
69
+ Apply a filter to this model
70
+ :param filter_args: A list of filters information to apply, passed as a request URL parameter.
71
+ Each filter object has the following fields:
72
+ - name: The name of the field you want to filter on.
73
+ - op: The operation you want to use (all sqlalchemy operations are available). The valid values are:
74
+ - like: Invoke SQL like (or "ilike", "match", "notilike")
75
+ - eq: check if field is equal to something
76
+ - ge: check if field is greater than or equal to something
77
+ - gt: check if field is greater than to something
78
+ - ne: check if field is not equal to something
79
+ - is_: check if field is a value
80
+ - is_not: check if field is not a value
81
+ - le: check if field is less than or equal to something
82
+ - lt: check if field is less than to something
83
+ - val: The value that you want to compare.
84
+ :return: sqla query object
85
+ """
86
+ try:
87
+ filters = json.loads(filter_args[0])
88
+ except json.decoder.JSONDecodeError:
89
+ raise ValidationError("Invalid filter format (see https://github.com/thomaxxl/safrs/wiki)")
90
+
91
+ if not isinstance(filters, list):
92
+ filters = [filters]
93
+
94
+ expressions = []
95
+ query = cls._s_query
96
+
97
+ for filt in filters:
98
+ if not isinstance(filt, dict):
99
+ safrs.log.warning(f"Invalid filter '{filt}'")
100
+ continue
101
+ attr_name = filt.get("name")
102
+ attr_val = filt.get("val")
103
+ if attr_name != "id" and attr_name not in cls._s_jsonapi_attrs:
104
+ raise ValidationError(f'Invalid filter "{filt}", unknown attribute "{attr_name}"')
105
+
106
+ op_name = filt.get("op", "").strip("_")
107
+ attr = cls._s_jsonapi_attrs[attr_name] if attr_name != "id" else cls.id
108
+ if op_name in ["in", "notin"]:
109
+ op = getattr(attr, op_name + "_")
110
+ query = query.filter(op(attr_val))
111
+ elif op_name in ["like", "ilike", "match", "notilike"] and hasattr(attr, "like"):
112
+ # => attr is Column or InstrumentedAttribute
113
+ like = getattr(attr, op_name)
114
+ expressions.append(like(attr_val))
115
+ elif not hasattr(operator, op_name):
116
+ raise ValidationError(f'Invalid filter "{filt}", unknown operator "{op_name}"')
117
+ else:
118
+ op = getattr(operator, op_name)
119
+ expressions.append(op(attr, attr_val))
120
+
121
+ if len(filters) > 1:
122
+ return query.filter(operator.and_(*expressions))
123
+ else:
124
+ return query.filter(*expressions)
125
+
126
+
61
127
  class TestBase(Base):
62
128
  __abstract__ = True
63
129
  def __init__(self, *args, **kwargs):