ApiLogicServer 14.4.0__py3-none-any.whl → 14.5.0__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.
- api_logic_server_cli/add_cust/add_cust.py +283 -0
- api_logic_server_cli/api_logic_server.py +15 -237
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/cli.py +38 -28
- api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/dbml.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
- api_logic_server_cli/create_from_model/api_logic_server_utils.py +47 -0
- api_logic_server_cli/create_from_model/dbml.py +113 -58
- api_logic_server_cli/create_from_model/ont_build.py +83 -60
- api_logic_server_cli/create_from_model/ont_create.py +2 -1
- api_logic_server_cli/database/basic_demo.sqlite +0 -0
- api_logic_server_cli/database/basic_demo.txt +1 -0
- api_logic_server_cli/database/basic_demo_wg.sqlite +0 -0
- api_logic_server_cli/manager.py +3 -2
- api_logic_server_cli/prototypes/base/.vscode/launch.json +3 -2
- api_logic_server_cli/prototypes/base/config/config.py +66 -11
- api_logic_server_cli/prototypes/base/config/default.env +7 -1
- api_logic_server_cli/prototypes/base/database/test_data/readme.md +2 -1
- api_logic_server_cli/prototypes/base/integration/kafka/kafka_producer.py +5 -2
- api_logic_server_cli/prototypes/base/integration/n8n/n8n_producer.py +68 -21
- api_logic_server_cli/prototypes/base/integration/n8n/n8n_readme.md +19 -0
- api_logic_server_cli/prototypes/base/test/basic/server_test.py +1 -1
- api_logic_server_cli/prototypes/basic_demo/README.md +29 -52
- api_logic_server_cli/prototypes/basic_demo/customizations/api/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/mcp_server_executor.py +138 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/openapi.py +92 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/proper_update_def.json +71 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/config/default.env +13 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/database/models.py +131 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/.DS_Store +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/1_langchain_loader.py +71 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/README_mcp.md +13 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py +295 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_schema.txt +47 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_server_discovery.json +9 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_flow.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/multi_mcp_orchestration.yaml +49 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/multi_mcp_flow/wny mcp flows.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/natlang_to_api.py +73 -0
- api_logic_server_cli/prototypes/{nw_no_cust → basic_demo/customizations}/integration/mcp/resources/curl.txt +2 -1
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP Overview.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Arch.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/images/MCP_Overview_Executor.png +0 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/1 - prompt_messages_array.json +10 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/invoke_llm/2 - completion_tool_context.json +12 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/llm_schema.txt +38 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_2.yaml +17393 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/nw_swagger_3_relaxed.yaml +109 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_server.py +51 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/proxy_serverZ.py +72 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/resources/validate_jsonapi.py +64 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/swagger_converter.py +65 -0
- api_logic_server_cli/prototypes/{nw_no_cust/integration/mcp → basic_demo/customizations/integration/mcp/z_old}/3_executor_test_agent.py +20 -6
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/3_executor_test_agent.py +52 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/README_functon.md +201 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/ai_plugin.json +17 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/nw-swagger_3.json +1731 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/snippets.txt +5 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3 genai_demo_with_get.json +1731 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3.json +1782 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo.json +264 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/integration/openai_function/swagger_3_genai_demo_with_update.json +1782 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/logic/declare_logic.py +62 -44
- api_logic_server_cli/prototypes/basic_demo/customizations/security/declare_security.py +11 -12
- api_logic_server_cli/prototypes/basic_demo/customizations/ui/admin/admin.yaml +166 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/api/{customize_api.py → api_discovery/order_b2b.py} +17 -23
- api_logic_server_cli/prototypes/basic_demo/iteration/database/db.sqlite +0 -0
- api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderB2B.py +6 -5
- api_logic_server_cli/prototypes/basic_demo/iteration/integration/row_dict_maps/OrderShipping.py +4 -4
- api_logic_server_cli/prototypes/basic_demo/iteration/logic/declare_logic.py +69 -43
- api_logic_server_cli/prototypes/basic_demo/iteration/ui/admin/admin.yaml +125 -50
- api_logic_server_cli/prototypes/manager/README.md +4 -0
- api_logic_server_cli/prototypes/manager/README_X.md +663 -0
- api_logic_server_cli/prototypes/nw_no_cust/Tutorial.md +45 -26
- api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/openapi.py +130 -0
- api_logic_server_cli/prototypes/nw_no_cust/api/api_discovery/proper_update_def.json +71 -0
- api_logic_server_cli/prototypes/nw_no_cust/config/default.env +13 -0
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/package-lock.json +9725 -1180
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/package.json +3 -6
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/app/shared/app.services.config.ts +1 -1
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/css/app.scss +4 -0
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/en.json +1 -1
- api_logic_server_cli/prototypes/ont_app/ontimize_seed/src/assets/i18n/es.json +14 -12
- api_logic_server_cli/prototypes/ont_app/templates/app_config.jinja +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/date_template.html +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/textarea_template.html +1 -1
- api_logic_server_cli/prototypes/ont_app/templates/timestamp_template.html +1 -1
- api_logic_server_cli/prototypes/sample_ai/logic/declare_logic.py +30 -13
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/METADATA +2 -2
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/RECORD +101 -60
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/WHEEL +1 -1
- api_logic_server_cli/prototypes/basic_demo/apply_customizations.ps1 +0 -17
- api_logic_server_cli/prototypes/basic_demo/apply_customizations.sh +0 -14
- api_logic_server_cli/prototypes/basic_demo/apply_iteration.ps1 +0 -20
- api_logic_server_cli/prototypes/basic_demo/apply_iteration.sh +0 -15
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/1_langchain_loader.py +0 -19
- api_logic_server_cli/prototypes/nw_no_cust/integration/mcp/README.md +0 -17
- /api_logic_server_cli/prototypes/{nw_no_cust → basic_demo/customizations}/integration/mcp/2_gpt_mcp_prompt.txt +0 -0
- /api_logic_server_cli/prototypes/{nw_no_cust → basic_demo/customizations}/integration/mcp/resources/nw_swagger_3.yaml +0 -0
- /api_logic_server_cli/prototypes/{nw_no_cust → basic_demo/customizations}/integration/mcp/run_executor.py +0 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/entry_points.txt +0 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-14.4.0.dist-info → apilogicserver-14.5.0.dist-info}/top_level.txt +0 -0
api_logic_server_cli/prototypes/basic_demo/customizations/api/api_discovery/proper_update_def.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"/Customer/{id}": {
|
|
3
|
+
"patch": {
|
|
4
|
+
"summary": "Update Customer by ID",
|
|
5
|
+
"operationId": "updateCustomer",
|
|
6
|
+
"parameters": [
|
|
7
|
+
{
|
|
8
|
+
"name": "id",
|
|
9
|
+
"in": "path",
|
|
10
|
+
"required": true,
|
|
11
|
+
"schema": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"requestBody": {
|
|
17
|
+
"required": true,
|
|
18
|
+
"content": {
|
|
19
|
+
"application/vnd.api+json": {
|
|
20
|
+
"schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["data"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"data": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["type", "id", "attributes"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"type": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["Customer"]
|
|
31
|
+
},
|
|
32
|
+
"id": {
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
35
|
+
"attributes": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"name": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"credit_limit": {
|
|
42
|
+
"type": "number"
|
|
43
|
+
},
|
|
44
|
+
"balance": {
|
|
45
|
+
"type": "number"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"responses": {
|
|
57
|
+
"200": {
|
|
58
|
+
"description": "Customer updated",
|
|
59
|
+
"content": {
|
|
60
|
+
"application/vnd.api+json": {
|
|
61
|
+
"schema": {
|
|
62
|
+
"type": "object"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
SECRET_KEY = "whatnothow"
|
|
2
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
3
|
+
SQLAlCHEMY_ECHO = False
|
|
4
|
+
# AGGREGATE_DEFAULTS = True
|
|
5
|
+
# ALL_DEFAULTS = True
|
|
6
|
+
# APILOGICPROJECT_KAFKA_PRODUCER = "{\"bootstrap.servers\": \"localhost:9092\"}"
|
|
7
|
+
# SQLALCHEMY_DATABASE_URI=db.sqlite
|
|
8
|
+
|
|
9
|
+
SECURITY_ENABLED = false
|
|
10
|
+
|
|
11
|
+
# if using tunnel for mcp, function, or ai_plugin
|
|
12
|
+
# eg, https://tunnel_url.ngrok-free.app
|
|
13
|
+
API_LOGIC_SERVER_TUNNEL = "TUNNEL_URL"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
from sqlalchemy import DECIMAL, DateTime # API Logic Server GenAI assist
|
|
3
|
+
from sqlalchemy import Boolean, Column, DECIMAL, Date, ForeignKey, Integer, String
|
|
4
|
+
from sqlalchemy.orm import relationship
|
|
5
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
6
|
+
|
|
7
|
+
########################################################################################################################
|
|
8
|
+
# Classes describing database for SqlAlchemy ORM, initially created by schema introspection.
|
|
9
|
+
#
|
|
10
|
+
# Alter this file per your database maintenance policy
|
|
11
|
+
# See https://apilogicserver.github.io/Docs/Project-Rebuild/#rebuilding
|
|
12
|
+
#
|
|
13
|
+
# Created: May 14, 2025 10:47:47
|
|
14
|
+
# Database: sqlite:////Users/val/dev/ApiLogicServer/ApiLogicServer-dev/servers/basic_demo/database/db.sqlite
|
|
15
|
+
# Dialect: sqlite
|
|
16
|
+
#
|
|
17
|
+
# mypy: ignore-errors
|
|
18
|
+
########################################################################################################################
|
|
19
|
+
|
|
20
|
+
from database.system.SAFRSBaseX import SAFRSBaseX, TestBase
|
|
21
|
+
from flask_login import UserMixin
|
|
22
|
+
import safrs, flask_sqlalchemy, os
|
|
23
|
+
from safrs import jsonapi_attr
|
|
24
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
25
|
+
from sqlalchemy.orm import relationship
|
|
26
|
+
from sqlalchemy.orm import Mapped
|
|
27
|
+
from sqlalchemy.sql.sqltypes import NullType
|
|
28
|
+
from typing import List
|
|
29
|
+
|
|
30
|
+
db = SQLAlchemy()
|
|
31
|
+
Base = declarative_base() # type: flask_sqlalchemy.model.DefaultMeta
|
|
32
|
+
metadata = Base.metadata
|
|
33
|
+
|
|
34
|
+
#NullType = db.String # datatype fixup
|
|
35
|
+
#TIMESTAMP= db.TIMESTAMP
|
|
36
|
+
|
|
37
|
+
from sqlalchemy.dialects.sqlite import *
|
|
38
|
+
|
|
39
|
+
if os.getenv('APILOGICPROJECT_NO_FLASK') is None or os.getenv('APILOGICPROJECT_NO_FLASK') == 'None':
|
|
40
|
+
Base = SAFRSBaseX # enables rules to be used outside of Flask, e.g., test data loading
|
|
41
|
+
else:
|
|
42
|
+
Base = TestBase # ensure proper types, so rules work for data loading
|
|
43
|
+
print('*** Models.py Using TestBase ***')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Customer(Base): # type: ignore
|
|
48
|
+
__tablename__ = 'customer'
|
|
49
|
+
_s_collection_name = 'Customer' # type: ignore
|
|
50
|
+
|
|
51
|
+
id = Column(Integer, primary_key=True)
|
|
52
|
+
name = Column(String)
|
|
53
|
+
balance : DECIMAL = Column(DECIMAL)
|
|
54
|
+
credit_limit : DECIMAL = Column(DECIMAL)
|
|
55
|
+
email = Column(String)
|
|
56
|
+
email_opt_out = Column(Boolean)
|
|
57
|
+
|
|
58
|
+
# parent relationships (access parent)
|
|
59
|
+
|
|
60
|
+
# child relationships (access children)
|
|
61
|
+
EmailList : Mapped[List["Email"]] = relationship(back_populates="customer")
|
|
62
|
+
OrderList : Mapped[List["Order"]] = relationship(back_populates="customer")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Product(Base): # type: ignore
|
|
67
|
+
__tablename__ = 'product'
|
|
68
|
+
_s_collection_name = 'Product' # type: ignore
|
|
69
|
+
|
|
70
|
+
id = Column(Integer, primary_key=True)
|
|
71
|
+
name = Column(String)
|
|
72
|
+
unit_price : DECIMAL = Column(DECIMAL)
|
|
73
|
+
|
|
74
|
+
# parent relationships (access parent)
|
|
75
|
+
|
|
76
|
+
# child relationships (access children)
|
|
77
|
+
ItemList : Mapped[List["Item"]] = relationship(back_populates="product")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Email(Base): # type: ignore
|
|
82
|
+
__tablename__ = 'email'
|
|
83
|
+
_s_collection_name = 'Email' # type: ignore
|
|
84
|
+
|
|
85
|
+
id = Column(Integer, primary_key=True)
|
|
86
|
+
message = Column(String)
|
|
87
|
+
customer_id = Column(ForeignKey('customer.id'), nullable=False)
|
|
88
|
+
CreatedOn = Column(Date)
|
|
89
|
+
|
|
90
|
+
# parent relationships (access parent)
|
|
91
|
+
customer : Mapped["Customer"] = relationship(back_populates=("EmailList"))
|
|
92
|
+
|
|
93
|
+
# child relationships (access children)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Order(Base): # type: ignore
|
|
98
|
+
__tablename__ = 'order'
|
|
99
|
+
_s_collection_name = 'Order' # type: ignore
|
|
100
|
+
|
|
101
|
+
id = Column(Integer, primary_key=True)
|
|
102
|
+
notes = Column(String)
|
|
103
|
+
customer_id = Column(ForeignKey('customer.id'), nullable=False)
|
|
104
|
+
CreatedOn = Column(Date)
|
|
105
|
+
date_shipped = Column(Date)
|
|
106
|
+
amount_total : DECIMAL = Column(DECIMAL)
|
|
107
|
+
|
|
108
|
+
# parent relationships (access parent)
|
|
109
|
+
customer : Mapped["Customer"] = relationship(back_populates=("OrderList"))
|
|
110
|
+
|
|
111
|
+
# child relationships (access children)
|
|
112
|
+
ItemList : Mapped[List["Item"]] = relationship(back_populates="order")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Item(Base): # type: ignore
|
|
117
|
+
__tablename__ = 'item'
|
|
118
|
+
_s_collection_name = 'Item' # type: ignore
|
|
119
|
+
|
|
120
|
+
id = Column(Integer, primary_key=True)
|
|
121
|
+
order_id = Column(ForeignKey('order.id'))
|
|
122
|
+
product_id = Column(ForeignKey('product.id'), nullable=False)
|
|
123
|
+
quantity = Column(Integer, nullable=False)
|
|
124
|
+
amount : DECIMAL = Column(DECIMAL)
|
|
125
|
+
unit_price : DECIMAL = Column(DECIMAL)
|
|
126
|
+
|
|
127
|
+
# parent relationships (access parent)
|
|
128
|
+
order : Mapped["Order"] = relationship(back_populates=("ItemList"))
|
|
129
|
+
product : Mapped["Product"] = relationship(back_populates=("ItemList"))
|
|
130
|
+
|
|
131
|
+
# child relationships (access children)
|
|
Binary file
|
api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/1_langchain_loader.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==========================================================================================
|
|
3
|
+
LangChain OpenAPI Loader – JSON:API Planner Tool (1_langchain_loader.py)
|
|
4
|
+
==========================================================================================
|
|
5
|
+
|
|
6
|
+
Alert:
|
|
7
|
+
------
|
|
8
|
+
This does not work - hours of import version madness.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Purpose:
|
|
12
|
+
--------
|
|
13
|
+
This script loads an OpenAPI 3.0 specification (e.g., nw_swagger_3.yaml) using LangChain's
|
|
14
|
+
OpenAPI tools to dynamically generate a structured LangChain Tool. It can be used as a
|
|
15
|
+
building block in an AI system that automatically interprets natural language goals and
|
|
16
|
+
calls API endpoints by mapping them to OpenAPI operations.
|
|
17
|
+
|
|
18
|
+
How It's Used:
|
|
19
|
+
--------------
|
|
20
|
+
- Reads the OpenAPI schema and extracts available endpoints.
|
|
21
|
+
- Instantiates a tool for a specific operation (e.g., getCustomer).
|
|
22
|
+
- Allows GPT agents or scripts to call the tool using structured input.
|
|
23
|
+
- Typically used in multi-agent or planner + executor systems (e.g., AutoGen, LangGraph).
|
|
24
|
+
- It is *not* required to run integration/mcp/3_executor_test_agent.py
|
|
25
|
+
|
|
26
|
+
Limitations:
|
|
27
|
+
------------
|
|
28
|
+
- This script does not itself parse natural language input.
|
|
29
|
+
- The operation_id must be known and match one in the OpenAPI spec.
|
|
30
|
+
- Requires LangChain and OpenAPI tool support installed.
|
|
31
|
+
|
|
32
|
+
Dependencies:
|
|
33
|
+
-------------
|
|
34
|
+
- langchain
|
|
35
|
+
- openapi-spec-validator
|
|
36
|
+
|
|
37
|
+
Recommended Next Step:
|
|
38
|
+
----------------------
|
|
39
|
+
Use this in conjunction with a GPT planner that:
|
|
40
|
+
1. Converts natural language into structured intent.
|
|
41
|
+
2. Selects the correct operation_id.
|
|
42
|
+
3. Provides parameters as expected by the OpenAPI operation.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from langchain.tools.openapi.utils.openapi_utils import OpenAPISpec
|
|
47
|
+
from langchain.tools.openapi.tool import OpenAPITool
|
|
48
|
+
|
|
49
|
+
# Load your OpenAPI spec
|
|
50
|
+
spec = OpenAPISpec.from_file("resources/nw_swagger_3.yaml")
|
|
51
|
+
spec = OpenAPISpec.from_file("resources/nw_swagger_2.yaml")
|
|
52
|
+
|
|
53
|
+
# List available operation IDs (optional debugging)
|
|
54
|
+
print("Available operation IDs:")
|
|
55
|
+
for path, methods in spec.raw_spec["paths"].items():
|
|
56
|
+
for method, details in methods.items():
|
|
57
|
+
print(f"- {details.get('operationId')}")
|
|
58
|
+
|
|
59
|
+
# Create LangChain tool from a specific endpoint
|
|
60
|
+
customer_tool = OpenAPITool.from_openapi_spec(
|
|
61
|
+
spec=spec,
|
|
62
|
+
operation_id="getCustomer", # use actual operationId from your spec
|
|
63
|
+
verbose=True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Run a test call
|
|
67
|
+
result = customer_tool.run({
|
|
68
|
+
"filter[Country]": "USA",
|
|
69
|
+
"page[limit]": 3
|
|
70
|
+
})
|
|
71
|
+
print(result)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Model Context Protocol is a way for:
|
|
2
|
+
|
|
3
|
+
1. **Bus User ad hoc flows** using existing published mcp services (vs. hard-coding in IT as an endpoint; flows can be cached for repeated use)
|
|
4
|
+
|
|
5
|
+
* ***Natural Language access*** to corporate databases for improved user interfaces
|
|
6
|
+
|
|
7
|
+
* LLMs ***choreograph*** multiple MCP calls (to 1 or more MCP servers) in a chain of calls - an agentic workflow. MCPs support shared contexts and goals, enabling the LLM to use the result from 1 call to determine whether the goals has been reached, or which service is appropriate to call next
|
|
8
|
+
|
|
9
|
+
3. Chat agents to ***discover*** and ***call*** external servers, be they databases, APIs, file systems, etc. MCPs support shared contexts and goals, enabling the LLM
|
|
10
|
+
|
|
11
|
+
* ***Corporate database participation*** in such flows, by making key functions available as MCP calls.
|
|
12
|
+
|
|
13
|
+
This example is [explained here](https://apilogicserver.github.io/Docs/Integration-MCP/).
|
api_logic_server_cli/prototypes/basic_demo/customizations/integration/mcp/mcp_client_executor.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This simulates the MCP Client Executor,
|
|
3
|
+
which takes a natural language query and converts it into a tool context block:
|
|
4
|
+
|
|
5
|
+
1. Discovers MCP servers (from config)
|
|
6
|
+
2. Queries OpenAI's GPT-4 model to obtain the tool context based on a provided schema and a natural language query
|
|
7
|
+
3. Processes the tool context (calls the indicated MCP (als) endpoints)
|
|
8
|
+
|
|
9
|
+
Notes:
|
|
10
|
+
* See: integration/mcp/README_mcp.md
|
|
11
|
+
* python api_logic_server_run.py
|
|
12
|
+
|
|
13
|
+
ToDo - email example is incomplete:
|
|
14
|
+
1. Add email event handler (ala nw_sample/logic/declare_logic.py#send_n8n_message())
|
|
15
|
+
2. And, respect the customer email_opt_out
|
|
16
|
+
3. Needs to use date range
|
|
17
|
+
4. Data incomplete
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import openai
|
|
24
|
+
import requests
|
|
25
|
+
|
|
26
|
+
# Set your OpenAI API key
|
|
27
|
+
openai.api_key = os.getenv("APILOGICSERVER_CHATGPT_APIKEY")
|
|
28
|
+
|
|
29
|
+
server_url = os.getenv("APILOGICSERVER_URL", "http://localhost:5656/api")
|
|
30
|
+
|
|
31
|
+
# debug settings
|
|
32
|
+
test_type = 'orchestration' # 'simple_get' or 'orchestration'
|
|
33
|
+
create_tool_context_from_llm = True
|
|
34
|
+
''' set to False to bypass LLM call and save 2-3 secs in testing '''
|
|
35
|
+
use_test_schema = False
|
|
36
|
+
''' True means bypass discovery, use hard-coded schedma file '''
|
|
37
|
+
|
|
38
|
+
def discover_mcp_servers():
|
|
39
|
+
""" Discover the MCP servers by calling the /api/.well-known/mcp.json endpoint.
|
|
40
|
+
This function retrieves the list of available MCP servers and their capabilities.
|
|
41
|
+
"""
|
|
42
|
+
global server_url, use_test_schema
|
|
43
|
+
|
|
44
|
+
# create schema_text (for prompt), by reading integration/mcp/mcp_schema.txt
|
|
45
|
+
if use_test_schema:
|
|
46
|
+
schema_file_path = os.path.join(os.path.dirname(__file__), "mcp_schema.txt")
|
|
47
|
+
try:
|
|
48
|
+
with open(schema_file_path, "r") as schema_file:
|
|
49
|
+
schema_text = schema_file.read()
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
print(f"Schema file not found at {schema_file_path}.")
|
|
52
|
+
exit(1)
|
|
53
|
+
finally:
|
|
54
|
+
print(f"Schema file loaded from {schema_file_path}.")
|
|
55
|
+
return schema_text
|
|
56
|
+
|
|
57
|
+
# find the servers - read the mcp_server_discovery.json file
|
|
58
|
+
discovery_file_path = os.path.join(os.path.dirname(__file__), "mcp_server_discovery.json")
|
|
59
|
+
try:
|
|
60
|
+
with open(discovery_file_path, "r") as discovery_file:
|
|
61
|
+
discovery_data = json.load(discovery_file)
|
|
62
|
+
print(f"\nDiscovered MCP servers from config file: {discovery_file_path}:" + json.dumps(discovery_data, indent=4))
|
|
63
|
+
except FileNotFoundError:
|
|
64
|
+
print(f"Discovery file not found at {discovery_file_path}.")
|
|
65
|
+
except json.JSONDecodeError as e:
|
|
66
|
+
print(f"Error decoding JSON from {discovery_file_path}: {e}")
|
|
67
|
+
|
|
68
|
+
for each_server in discovery_data["servers"]:
|
|
69
|
+
discovery_url = each_server["schema_url"]
|
|
70
|
+
|
|
71
|
+
# Call the discovery_url to get the MCP/API schema
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(discovery_url)
|
|
74
|
+
if response.status_code == 200:
|
|
75
|
+
api_schema = response.json()
|
|
76
|
+
print()
|
|
77
|
+
print(f"\n1. API Schema from discovery schema_url: {discovery_url}:\n" +json.dumps(api_schema, indent=4))
|
|
78
|
+
else:
|
|
79
|
+
print(f"Failed to retrieve API schema from {discovery_url}: {response.status_code}")
|
|
80
|
+
except requests.RequestException as e:
|
|
81
|
+
print(f"Error calling OpenAPI URL: {e}")
|
|
82
|
+
return json.dumps(api_schema)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_user_nl_query():
|
|
86
|
+
""" Get the natural language query from the user. """
|
|
87
|
+
|
|
88
|
+
global test_type
|
|
89
|
+
# this doesn't work -- missing commands for mcp_server_executor....
|
|
90
|
+
default_request = "List the orders created more than 30 days ago, and post an email message to the order's customer offering a discount"
|
|
91
|
+
|
|
92
|
+
default_request = "List the orders created more than 30 days ago, and send a discount email to the customer for each one."
|
|
93
|
+
# date range? curl -X GET "http://localhost:5656/api/Order?filter=[{\’name\'}: {\’CreatedOn\’}, {\’op\’}: {\’gt\’}, {\’val\’}: {\’2022-05-14\’}]”
|
|
94
|
+
|
|
95
|
+
default_request = "List the orders for customer 5, and send a discount email to the customer for each one."
|
|
96
|
+
|
|
97
|
+
if test_type != 'orchestration':
|
|
98
|
+
default_request = "List customers with credit over 1000"
|
|
99
|
+
|
|
100
|
+
query = sys.argv[1] if len(sys.argv) > 1 else default_request
|
|
101
|
+
|
|
102
|
+
query += """
|
|
103
|
+
Respond with a JSON array of tool context blocks using:
|
|
104
|
+
- tool: 'json-api'
|
|
105
|
+
- JSON:API-compliant filtering (e.g., filter[CreatedOn][lt])
|
|
106
|
+
- Use {{ order.customer_id }} as a placeholder in the second step.
|
|
107
|
+
- Include method, url, query_params or body, headers, expected_output.
|
|
108
|
+
"""
|
|
109
|
+
return query
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def query_llm_with_nl(nl_query):
|
|
113
|
+
"""
|
|
114
|
+
Query the LLM with a natural language query and schema text to generate a tool context block.
|
|
115
|
+
|
|
116
|
+
It handles both orchestration and simple GET requests.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
global test_type, create_tool_context_from_llm
|
|
120
|
+
|
|
121
|
+
messages = [
|
|
122
|
+
{
|
|
123
|
+
"role": "system",
|
|
124
|
+
"content": "You are an API planner that converts natural language queries into MCP Tool Context blocks using JSON:API. Return only the tool context as JSON."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"role": "user",
|
|
128
|
+
"content": f"Schema:\n{schema_text}\n\nNatural language query: '{nl_query}'"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# setup default tool_context to bypass LLM call and save 2-3 secs in testing
|
|
133
|
+
if test_type == 'orchestration': # orchestration: emails to pending orders
|
|
134
|
+
tool_context = \
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
"tool": "json-api",
|
|
138
|
+
"method": "GET",
|
|
139
|
+
"url": "http://localhost:5656/api/Order",
|
|
140
|
+
"query_params": {
|
|
141
|
+
"filter[customer_id]": 5
|
|
142
|
+
},
|
|
143
|
+
"headers": {
|
|
144
|
+
"Content-Type": "application/vnd.api+json"
|
|
145
|
+
},
|
|
146
|
+
"expected_output": "JSON array of orders for customer 5"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"tool": "email",
|
|
150
|
+
"method": "POST",
|
|
151
|
+
"url": "http://localhost:5656/api/Email",
|
|
152
|
+
"body": {
|
|
153
|
+
"to": "{{ order.customer_id }}",
|
|
154
|
+
"subject": "Discount Offer",
|
|
155
|
+
"message": "Dear Customer, We are offering a discount on your recent orders. Please check your account for more details."
|
|
156
|
+
},
|
|
157
|
+
"headers": {
|
|
158
|
+
"Content-Type": "application/json"
|
|
159
|
+
},
|
|
160
|
+
"expected_output": "Email sent confirmation"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
else: # simple get request - list customers with credit over 4000
|
|
164
|
+
tool_context = \
|
|
165
|
+
[
|
|
166
|
+
{
|
|
167
|
+
"tool": "json-api",
|
|
168
|
+
"method": "GET",
|
|
169
|
+
"url": "http://localhost:5656/api/Customer",
|
|
170
|
+
"query_params": {
|
|
171
|
+
"filter[credit_limit][gt]": 1000
|
|
172
|
+
},
|
|
173
|
+
"headers": {
|
|
174
|
+
"Content-Type": "application/vnd.api+json"
|
|
175
|
+
},
|
|
176
|
+
"expected_output": "JSON array of customers with credit limit over 1000"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
# Call the OpenAI API to generate the tool context
|
|
181
|
+
if create_tool_context_from_llm: # saves 2-3 seconds...
|
|
182
|
+
response = openai.chat.completions.create(
|
|
183
|
+
model="gpt-4",
|
|
184
|
+
messages=messages,
|
|
185
|
+
temperature=0.2
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
tool_context_str = response.choices[0].message.content
|
|
189
|
+
try:
|
|
190
|
+
tool_context = json.loads(tool_context_str)
|
|
191
|
+
except json.JSONDecodeError:
|
|
192
|
+
print("Failed to decode JSON from response:", tool_context_str)
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
print("\n2. generated tool context from LLM:\n", json.dumps(tool_context, indent=4))
|
|
196
|
+
return tool_context
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def process_tool_context(tool_context):
|
|
200
|
+
""" Process the orchestration request by executing multiple tool context blocks.
|
|
201
|
+
This function simulates the MCP Client Executor by executing the tool context blocks
|
|
202
|
+
against a live JSON:API server. It handles both GET and POST requests, and it can
|
|
203
|
+
orchestrate multiple requests based on the provided tool context.
|
|
204
|
+
|
|
205
|
+
Note the orchestration is processed by the client executor (here), not the server executor.
|
|
206
|
+
|
|
207
|
+
Research:
|
|
208
|
+
|
|
209
|
+
1. How is this a "USB", since the request was specific about JSON:API?
|
|
210
|
+
2. How is it clear to loop through the tool_context[0] and call tool_context[1]?
|
|
211
|
+
"""
|
|
212
|
+
global server_url
|
|
213
|
+
|
|
214
|
+
def get_query_param_filter(query_params):
|
|
215
|
+
""" return json:api filter
|
|
216
|
+
|
|
217
|
+
query_params might be:
|
|
218
|
+
"query_params": {
|
|
219
|
+
"filter[credit_limit][gt]": 1000 }
|
|
220
|
+
or:
|
|
221
|
+
"query_params": {
|
|
222
|
+
"filter[customer_id]": 5},
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
query_param_filter = ''
|
|
226
|
+
if isinstance(query_params, dict):
|
|
227
|
+
for each_key, each_value in query_params.items():
|
|
228
|
+
if isinstance(each_value, dict):
|
|
229
|
+
for sub_key, sub_value in each_value.items():
|
|
230
|
+
query_param_filter += f"&{each_key}[{sub_key}]={sub_value}"
|
|
231
|
+
else:
|
|
232
|
+
query_param_filter += f"&{each_key}={each_value}"
|
|
233
|
+
# query_params = ''
|
|
234
|
+
elif isinstance(query_params, dict):
|
|
235
|
+
assert False, "Query Params dict tbd"
|
|
236
|
+
return query_param_filter
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if isinstance(tool_context, dict):
|
|
240
|
+
query_params = tool_context["query_params"]
|
|
241
|
+
query_param_filter = get_query_param_filter(query_params)
|
|
242
|
+
mcp_response = requests.get(
|
|
243
|
+
tool_context["url"],
|
|
244
|
+
headers=tool_context["headers"],
|
|
245
|
+
params=query_param_filter
|
|
246
|
+
)
|
|
247
|
+
elif isinstance(tool_context, list):
|
|
248
|
+
context_data = {}
|
|
249
|
+
added_rows = 0
|
|
250
|
+
|
|
251
|
+
for each_block in tool_context:
|
|
252
|
+
if each_block["tool"] in ["json-api", "email"]:
|
|
253
|
+
if each_block["method"] == "GET":
|
|
254
|
+
query_param_filter = get_query_param_filter(each_block["query_params"])
|
|
255
|
+
mcp_response = requests.get(
|
|
256
|
+
url = each_block["url"],
|
|
257
|
+
headers=each_block["headers"],
|
|
258
|
+
params=query_param_filter
|
|
259
|
+
)
|
|
260
|
+
context_data = mcp_response.json()['data'] # result rows...
|
|
261
|
+
elif each_block["method"] in ["POST"]:
|
|
262
|
+
add_rows = 0
|
|
263
|
+
for each_order in context_data:
|
|
264
|
+
url = each_block["url"]
|
|
265
|
+
json_update_data = { 'data': {"type": "Email", 'attributes': {} } }
|
|
266
|
+
json_update_data_attributes = json_update_data["data"]["attributes"]
|
|
267
|
+
json_update_data_attributes["customer_id"] = context_data[0]['attributes']["customer_id"]
|
|
268
|
+
json_update_data_attributes["message"] = each_block["body"]["message"]
|
|
269
|
+
# eg: POST http://localhost:5656/api/Email {'data': {'type': 'Email', 'attributes': {'customer_id': 5, 'message': {'to': '{{ order.customer_id }}', 'subject': 'Discount for your order', 'body': 'Dear customer, you have a discount for your recent order. Thank you for shopping with us.'}}}}
|
|
270
|
+
mcp_response = requests.post(
|
|
271
|
+
url=url,
|
|
272
|
+
headers=each_block["headers"],
|
|
273
|
+
json=json_update_data
|
|
274
|
+
)
|
|
275
|
+
add_rows += 1
|
|
276
|
+
pass
|
|
277
|
+
else:
|
|
278
|
+
print("Invalid tool context format. Expected a dictionary or a list.")
|
|
279
|
+
return None
|
|
280
|
+
print("\n3. MCP Server (als) Response:\n", mcp_response.text)
|
|
281
|
+
return mcp_response
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
import sys
|
|
286
|
+
|
|
287
|
+
schema_text = discover_mcp_servers() # see: 1-discovery-from-als
|
|
288
|
+
|
|
289
|
+
query = get_user_nl_query()
|
|
290
|
+
|
|
291
|
+
tool_context = query_llm_with_nl(query) # see: 2-tool-context-from-LLM
|
|
292
|
+
|
|
293
|
+
mcp_response = process_tool_context(tool_context) # see: 3-MCP-server response
|
|
294
|
+
|
|
295
|
+
print("\nTest complete.\n")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Customer:
|
|
2
|
+
- id (INTEGER)
|
|
3
|
+
- name (VARCHAR)
|
|
4
|
+
- balance (DECIMAL)
|
|
5
|
+
- credit_limit (DECIMAL)
|
|
6
|
+
* Filterable: name, balance, credit_limit
|
|
7
|
+
* Example: 'List customers with credit over 5000'
|
|
8
|
+
|
|
9
|
+
Item:
|
|
10
|
+
- id (INTEGER)
|
|
11
|
+
- order_id (INTEGER)
|
|
12
|
+
- product_id (INTEGER)
|
|
13
|
+
- quantity (INTEGER)
|
|
14
|
+
- amount (DECIMAL)
|
|
15
|
+
- unit_price (DECIMAL)
|
|
16
|
+
* Filterable: order_id, product_id, unit_price
|
|
17
|
+
* Relationships:
|
|
18
|
+
↪ relates to Order
|
|
19
|
+
↪ relates to Product
|
|
20
|
+
* Example: 'Find all items for order 42'
|
|
21
|
+
|
|
22
|
+
Email:
|
|
23
|
+
- id (INTEGER)
|
|
24
|
+
- message (VARCHAR)
|
|
25
|
+
- customer_id (INTEGER)
|
|
26
|
+
* Relationships:
|
|
27
|
+
↪ relates to Customer
|
|
28
|
+
* Example: 'Post an message "text" for customer 2 means POST to this resource'
|
|
29
|
+
|
|
30
|
+
Order:
|
|
31
|
+
- id (INTEGER)
|
|
32
|
+
- notes (VARCHAR)
|
|
33
|
+
- customer_id (INTEGER)
|
|
34
|
+
- CreatedOn (DATE)
|
|
35
|
+
- date_shipped (DATE)
|
|
36
|
+
- amount_total (DECIMAL)
|
|
37
|
+
* Filterable: notes, customer_id, date_shipped, amount_total
|
|
38
|
+
* Relationships:
|
|
39
|
+
↪ relates to Customer
|
|
40
|
+
* Example: 'Get orders shipped in 2024'
|
|
41
|
+
|
|
42
|
+
Product:
|
|
43
|
+
- id (INTEGER)
|
|
44
|
+
- name (VARCHAR)
|
|
45
|
+
- unit_price (DECIMAL)
|
|
46
|
+
* Filterable: name, unit_price
|
|
47
|
+
* Example: 'Show products with price over 100'
|