restapi-mcp-server 0.0.1__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 (79) hide show
  1. restapi_mcp_server/__init__.py +4 -0
  2. restapi_mcp_server/__main__.py +328 -0
  3. restapi_mcp_server/src/__init__.py +0 -0
  4. restapi_mcp_server/src/api/__init__.py +0 -0
  5. restapi_mcp_server/src/api/router.py +17 -0
  6. restapi_mcp_server/src/api/v0_0_1/__init__.py +7 -0
  7. restapi_mcp_server/src/api/v0_0_1/routes/__init__.py +0 -0
  8. restapi_mcp_server/src/api/v0_0_1/routes/bas64interpolationRoute.py +57 -0
  9. restapi_mcp_server/src/api/v0_0_1/routes/healthRouter.py +7 -0
  10. restapi_mcp_server/src/api/v0_0_1/routes/jqRoute.py +29 -0
  11. restapi_mcp_server/src/api/v0_0_1/routes/restapiRoute.py +252 -0
  12. restapi_mcp_server/src/api/v0_0_1/routes/sessionRoute.py +25 -0
  13. restapi_mcp_server/src/api/v0_0_1/routes/transactionRoute.py +46 -0
  14. restapi_mcp_server/src/api/v0_0_1/routes/variablesRoute.py +87 -0
  15. restapi_mcp_server/src/constants/COMMON.py +25 -0
  16. restapi_mcp_server/src/constants/RESTAPI.py +7 -0
  17. restapi_mcp_server/src/constants/__init__.py +4 -0
  18. restapi_mcp_server/src/models/__init__.py +9 -0
  19. restapi_mcp_server/src/models/bas64interpolationSchema.py +35 -0
  20. restapi_mcp_server/src/models/jqSchema.py +25 -0
  21. restapi_mcp_server/src/models/restapiSchema.py +101 -0
  22. restapi_mcp_server/src/models/sessionSchema.py +7 -0
  23. restapi_mcp_server/src/models/transacationSchema.py +42 -0
  24. restapi_mcp_server/src/models/variablesSchema.py +65 -0
  25. restapi_mcp_server/src/services/__init__.py +0 -0
  26. restapi_mcp_server/src/services/restapi.py +499 -0
  27. restapi_mcp_server/src/services/transactions.py +142 -0
  28. restapi_mcp_server/src/services/variablesInterpolation.py +112 -0
  29. restapi_mcp_server/src/src/__init__.py +0 -0
  30. restapi_mcp_server/src/src/api/__init__.py +0 -0
  31. restapi_mcp_server/src/src/api/router.py +17 -0
  32. restapi_mcp_server/src/src/api/v0_0_1/__init__.py +7 -0
  33. restapi_mcp_server/src/src/api/v0_0_1/routes/__init__.py +0 -0
  34. restapi_mcp_server/src/src/api/v0_0_1/routes/bas64interpolationRoute.py +57 -0
  35. restapi_mcp_server/src/src/api/v0_0_1/routes/healthRouter.py +7 -0
  36. restapi_mcp_server/src/src/api/v0_0_1/routes/jqRoute.py +29 -0
  37. restapi_mcp_server/src/src/api/v0_0_1/routes/restapiRoute.py +34 -0
  38. restapi_mcp_server/src/src/api/v0_0_1/routes/sessionRoute.py +25 -0
  39. restapi_mcp_server/src/src/api/v0_0_1/routes/transactionRoute.py +46 -0
  40. restapi_mcp_server/src/src/api/v0_0_1/routes/variablesRoute.py +87 -0
  41. restapi_mcp_server/src/src/constants/COMMON.py +25 -0
  42. restapi_mcp_server/src/src/constants/RESTAPI.py +7 -0
  43. restapi_mcp_server/src/src/constants/__init__.py +4 -0
  44. restapi_mcp_server/src/src/models/__init__.py +9 -0
  45. restapi_mcp_server/src/src/models/bas64interpolationSchema.py +35 -0
  46. restapi_mcp_server/src/src/models/jqSchema.py +25 -0
  47. restapi_mcp_server/src/src/models/restapiSchema.py +54 -0
  48. restapi_mcp_server/src/src/models/sessionSchema.py +7 -0
  49. restapi_mcp_server/src/src/models/transacationSchema.py +42 -0
  50. restapi_mcp_server/src/src/models/variablesSchema.py +65 -0
  51. restapi_mcp_server/src/src/services/__init__.py +0 -0
  52. restapi_mcp_server/src/src/services/restapi.py +385 -0
  53. restapi_mcp_server/src/src/services/transactions.py +142 -0
  54. restapi_mcp_server/src/src/services/variablesInterpolation.py +112 -0
  55. restapi_mcp_server/src/src/utils/__init__.py +0 -0
  56. restapi_mcp_server/src/src/utils/bas64interpolation.py +111 -0
  57. restapi_mcp_server/src/src/utils/env.py +93 -0
  58. restapi_mcp_server/src/src/utils/idgen.py +23 -0
  59. restapi_mcp_server/src/src/utils/interpolation.py +60 -0
  60. restapi_mcp_server/src/src/utils/jqinterpolation.py +70 -0
  61. restapi_mcp_server/src/src/utils/jsoncodec.py +77 -0
  62. restapi_mcp_server/src/src/utils/logger.py +38 -0
  63. restapi_mcp_server/src/src/utils/persist.py +146 -0
  64. restapi_mcp_server/src/src/utils/restapi.py +233 -0
  65. restapi_mcp_server/src/utils/__init__.py +0 -0
  66. restapi_mcp_server/src/utils/bas64interpolation.py +111 -0
  67. restapi_mcp_server/src/utils/env.py +93 -0
  68. restapi_mcp_server/src/utils/idgen.py +23 -0
  69. restapi_mcp_server/src/utils/interpolation.py +60 -0
  70. restapi_mcp_server/src/utils/jqinterpolation.py +70 -0
  71. restapi_mcp_server/src/utils/jsoncodec.py +77 -0
  72. restapi_mcp_server/src/utils/logger.py +38 -0
  73. restapi_mcp_server/src/utils/persist.py +146 -0
  74. restapi_mcp_server/src/utils/restapi.py +269 -0
  75. restapi_mcp_server-0.0.1.dist-info/METADATA +96 -0
  76. restapi_mcp_server-0.0.1.dist-info/RECORD +79 -0
  77. restapi_mcp_server-0.0.1.dist-info/WHEEL +5 -0
  78. restapi_mcp_server-0.0.1.dist-info/entry_points.txt +2 -0
  79. restapi_mcp_server-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ """
2
+ RestAPI MCP Server package.
3
+ """
4
+ __version__ = "0.0.1"
@@ -0,0 +1,328 @@
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.exceptions import RequestValidationError
4
+ from .src.api.router import api_v001
5
+ from .src.constants import COMMON
6
+ import uvicorn
7
+ import os
8
+ from .src.utils.logger import setup_logging
9
+ from fastapi import Request
10
+ from mcp.server.fastmcp import FastMCP
11
+ from typing import Dict, List, Any
12
+ import httpx
13
+ import asyncio
14
+ import threading
15
+ from .src.utils.env import load_common_from_env
16
+ from pathlib import Path
17
+ from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
18
+
19
+ logger = setup_logging()
20
+
21
+ # Create the ASGI app at module scope so Uvicorn can import it via string path
22
+ app = FastAPI(
23
+ title="Rest API Orchestrator",
24
+ version="0.0.1",
25
+ docs_url="/api/docs",
26
+ redoc_url="/api/redoc",
27
+ openapi_url="/api/openapi.json",
28
+ )
29
+
30
+ # CORS: allow requests from any origin (any IP/host and any port) for all routes.
31
+ #
32
+ # Notes:
33
+ # - allow_origins=["*"] is sufficient to allow all hosts/ports.
34
+ # - If allow_credentials=True, FastAPI/starlette will NOT allow "*"; it must be
35
+ # an explicit list. We default credentials off for a true allow-all stance.
36
+ # - We also allow all methods/headers and expose all headers.
37
+ app.add_middleware(
38
+ CORSMiddleware,
39
+ allow_origins=["*"],
40
+ allow_credentials=False,
41
+ allow_methods=["*"],
42
+ allow_headers=["*"],
43
+ expose_headers=["*"],
44
+ max_age=86400,
45
+ )
46
+ app.include_router(api_v001)
47
+
48
+ # Guard error logging middleware to avoid double-send issues under certain ASGI flows
49
+ if os.getenv("ENABLE_ERROR_LOG_MW", "0").lower() in ("1", "true", "yes", "on"):
50
+ @app.middleware("http")
51
+ async def error_logging_middleware(request: Request, call_next):
52
+ try:
53
+ response = await call_next(request)
54
+ except Exception:
55
+ logger.exception("Unhandled exception: %s %s", request.method, request.url.path)
56
+ raise
57
+ if response.status_code >= 400:
58
+ logger.error("Request failed: %s %s -> %d", request.method, request.url.path, response.status_code)
59
+ return response
60
+
61
+ def startAppServer():
62
+ host = COMMON.HOST
63
+ port = COMMON.API_PORT
64
+ log_level = COMMON.log_level
65
+
66
+ # Use an import string that points to this module's app object
67
+ import_string = "restapi_mcp_server.__main__:app"
68
+
69
+ # Disable reload by default in container to avoid multi-process duplication issues
70
+ # Enable via UVICORN_RELOAD=1 if needed for local dev
71
+ reload_flag = os.getenv("UVICORN_RELOAD", "0").lower() in ("1", "true", "yes", "on")
72
+
73
+ uvicorn.run(
74
+ import_string,
75
+ host=host,
76
+ port=port,
77
+ log_level=log_level,
78
+ reload=reload_flag,
79
+ reload_dirs=["."] if reload_flag else None,
80
+ proxy_headers=False,
81
+ workers=1,
82
+ )
83
+
84
+
85
+ def startStaticIndexServer():
86
+ """Serve only index.html on a dedicated port (default :5500).
87
+
88
+ This keeps the FastAPI/Uvicorn port unchanged while allowing a simple UI server
89
+ suitable for local use and Docker.
90
+ """
91
+
92
+ ui_host = os.getenv("UI_HOST", "0.0.0.0")
93
+ ui_port = int(os.getenv("UI_PORT", "5500"))
94
+
95
+ index_path = Path(__file__).resolve().parent / "static" / "index.html"
96
+ if not index_path.exists():
97
+ logger.warning("[UI] index.html not found at %s; UI server will not start", index_path)
98
+ return
99
+
100
+ index_bytes = index_path.read_bytes()
101
+
102
+ class _IndexOnlyHandler(BaseHTTPRequestHandler):
103
+ def do_GET(self): # noqa: N802 (stdlib naming)
104
+ # Serve index for / or /index.html; 404 for everything else.
105
+ if self.path in ("/", "/index.html"):
106
+ self.send_response(200)
107
+ self.send_header("Content-Type", "text/html; charset=utf-8")
108
+ self.send_header("Content-Length", str(len(index_bytes)))
109
+ self.end_headers()
110
+ self.wfile.write(index_bytes)
111
+ return
112
+
113
+ self.send_response(404)
114
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
115
+ self.end_headers()
116
+ self.wfile.write(b"Not Found")
117
+
118
+ def log_message(self, format, *args): # noqa: N802 (stdlib naming)
119
+ # Route http.server logs through our logger (info level)
120
+ logger.info("[UI] %s - %s", self.address_string(), format % args)
121
+
122
+ def _run():
123
+ try:
124
+ httpd = ThreadingHTTPServer((ui_host, ui_port), _IndexOnlyHandler)
125
+ logger.info("[UI] Serving %s on http://%s:%d", index_path, ui_host, ui_port)
126
+ httpd.serve_forever()
127
+ except OSError as e:
128
+ logger.error("[UI] Failed to start UI server on %s:%d: %s", ui_host, ui_port, e)
129
+ except Exception:
130
+ logger.exception("[UI] UI server crashed")
131
+
132
+ threading.Thread(target=_run, name="ui-5500", daemon=True).start()
133
+
134
+ def createMCPServerWithTools():
135
+
136
+ mcp_host: str = COMMON.HOST
137
+ mcp_port: int = COMMON.MCP_API_PORT
138
+ # Allow overriding the orchestrator base URL (default stays local for dev)
139
+ # Set env var RESTAPI_ORCHESTRATOR_BASE (or ORCHESTRATOR_BASE) to target a remote host
140
+ API_BASE: str = os.getenv("RESTAPI_ORCHESTRATOR_BASE") or f"http://127.0.0.1:{COMMON.API_PORT}"
141
+ logger.info(f"[MCP] Orchestrator base: {API_BASE}")
142
+ mcp = FastMCP("restapi_orchestrator_tools", host=mcp_host, port=mcp_port)
143
+
144
+ @mcp.tool()
145
+ def createSession() -> Dict[str, Any]:
146
+ """Create a new session id.
147
+ Returns: { "session": string }
148
+ """
149
+ with httpx.Client(timeout=30.0) as client:
150
+ r = client.post(f"{API_BASE}/api/v001/session/createSession")
151
+ r.raise_for_status()
152
+ return r.json()
153
+
154
+ @mcp.tool()
155
+ def createEnvironmentVariables(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
156
+ """Upsert environment variables via REST API.
157
+
158
+ Endpoint: PUT /api/v001/variables/upsertEnvironmentVariable
159
+ Input: items: list of { "environment": str, "variable": str, "value": Any }
160
+ Returns: list of upserted items (echoed from API)
161
+ """
162
+ results: List[Dict[str, Any]] = []
163
+ with httpx.Client(timeout=30.0) as client:
164
+ for it in items:
165
+ r = client.put(f"{API_BASE}/api/v001/variables/upsertEnvironmentVariable", json=it)
166
+ r.raise_for_status()
167
+ results.append(r.json())
168
+ return results
169
+
170
+ @mcp.tool()
171
+ def upsertEnvironmentVariable(environment: str, variable: str, value: Any) -> Dict[str, Any]:
172
+ """Upsert a single environment variable via REST API.
173
+
174
+ Endpoint: PUT /api/v001/variables/upsertEnvironmentVariable
175
+ Inputs:
176
+ - environment: target environment name
177
+ - variable: variable name
178
+ - value: variable value (any JSON-serializable type)
179
+ Returns: { environment, variable, value }
180
+ """
181
+ payload: Dict[str, Any] = {
182
+ "environment": environment,
183
+ "variable": variable,
184
+ "value": value,
185
+ }
186
+ with httpx.Client(timeout=30.0) as client:
187
+ r = client.put(
188
+ f"{API_BASE}/api/v001/variables/upsertEnvironmentVariable",
189
+ json=payload,
190
+ )
191
+ r.raise_for_status()
192
+ return r.json()
193
+
194
+ @mcp.tool()
195
+ def listAllEnvironmentVariables(environment: str) -> List[Dict[str, Any]]:
196
+ """List all variables for the given environment.
197
+ Returns: array of { environment, variable, value }
198
+ """
199
+ with httpx.Client(timeout=30.0) as client:
200
+ r = client.get(
201
+ f"{API_BASE}/api/v001/variables/listAllEnvironmentVariables", params={"environment": environment}
202
+ )
203
+ r.raise_for_status()
204
+ return r.json()
205
+
206
+ @mcp.tool()
207
+ def listSpecificEnvironmentVariable(environment: str, variable: str) -> List[Dict[str, Any]]:
208
+ """Get a specific environment variable.
209
+ Returns: { environment, variable, value }
210
+ """
211
+ with httpx.Client(timeout=30.0) as client:
212
+ r = client.get(
213
+ f"{API_BASE}/api/v001/variables/listSpecificVariableByEnvironment",
214
+ params={"environment": environment, "variable": variable},
215
+ )
216
+ r.raise_for_status()
217
+ return r.json()
218
+
219
+ @mcp.tool()
220
+ def deleteAllByEnvironment(environment: str) -> Dict[str, Any]:
221
+ """Delete all variables for the given environment via REST API.
222
+
223
+ Endpoint: DELETE /api/v001/variables/deleteAllByEnvironment
224
+ Returns: { environment, deletedCount }
225
+ """
226
+ with httpx.Client(timeout=30.0) as client:
227
+ r = client.delete(
228
+ f"{API_BASE}/api/v001/variables/deleteAllByEnvironment",
229
+ params={"environment": environment},
230
+ )
231
+ r.raise_for_status()
232
+ return r.json()
233
+
234
+ @mcp.tool()
235
+ def health() -> Dict[str, Any]:
236
+ """Check API health.
237
+
238
+ Endpoint: GET /api/v001/health
239
+ Returns: {"status": "ok"} on healthy server.
240
+ """
241
+ with httpx.Client(timeout=10.0) as client:
242
+ r = client.get(f"{API_BASE}/api/v001/health")
243
+ r.raise_for_status()
244
+ return r.json()
245
+
246
+ @mcp.tool()
247
+ def createRestAPICall(
248
+ method: str,
249
+ url: str,
250
+ action: str,
251
+ environment: str,
252
+ session: str,
253
+ request_headers: Dict[str, Any] | None = None,
254
+ request_body: Any | None = None,
255
+ request_form_data: Dict[str, Any] | None = None,
256
+ request_files: List[Dict[str, Any]] | None = None,
257
+ pre_script: Any | None = None,
258
+ post_script: Any | None = None,
259
+ debug: bool | None = None,
260
+ ) -> Dict[str, Any]:
261
+ """Call the RestAPI Orchestrator HTTP endpoint.
262
+
263
+ Endpoint: POST /api/v001/restapi/call
264
+
265
+ Inputs:
266
+ - method: HTTP method (GET, POST, PUT, PATCH, DELETE)
267
+ - url: Target URL (supports variable/base64/jq interpolation server-side)
268
+ - action: Logical action name for transaction logging
269
+ - environment: Environment name
270
+ - session: Session identifier
271
+ - request_headers: Optional headers map (values may use interpolation syntax)
272
+ - request_body: Optional JSON body (values may use interpolation syntax)
273
+ - request_form_data: Optional form fields map for x-www-form-urlencoded or multipart requests
274
+ - request_files: Optional list of file parts. Each item: {field_name, filename, content_type?, content}
275
+ - pre_script: Reserved (not used currently)
276
+ - post_script: Optional dict of {"{{VARIABLE_NAME}}": "expression"}; evaluated on 2xx/3xx status
277
+ - debug: Optional flag
278
+
279
+ Returns: { response_status, response_headers, response_body }
280
+ """
281
+ payload: Dict[str, Any] = {
282
+ "method": method,
283
+ "url": url,
284
+ "action": action,
285
+ "environment": environment,
286
+ "session": session,
287
+ }
288
+ if request_headers is not None:
289
+ payload["request_headers"] = request_headers
290
+ if request_body is not None:
291
+ payload["request_body"] = request_body
292
+ if request_form_data is not None:
293
+ payload["request_form_data"] = request_form_data
294
+ if request_files is not None:
295
+ payload["request_files"] = request_files
296
+ if pre_script is not None:
297
+ payload["pre_script"] = pre_script
298
+ if post_script is not None:
299
+ payload["post_script"] = post_script
300
+ if debug is not None:
301
+ payload["debug"] = bool(debug)
302
+
303
+ with httpx.Client(timeout=60.0) as client:
304
+ r = client.post(f"{API_BASE}/api/v001/restapi/call", json=payload)
305
+ r.raise_for_status()
306
+ return r.json()
307
+
308
+ def _run_mcp_in_thread():
309
+ try:
310
+ loop = asyncio.new_event_loop()
311
+ asyncio.set_event_loop(loop)
312
+ mcp.run(transport="sse")
313
+ except Exception as e:
314
+ print(f"[MCP] MCP server thread exited: {e}")
315
+
316
+ thread = threading.Thread(target=_run_mcp_in_thread, name="mcp-sse", daemon=True)
317
+ thread.start()
318
+
319
+ def main():
320
+ load_common_from_env()
321
+ global logger
322
+ logger = setup_logging()
323
+ startStaticIndexServer()
324
+ createMCPServerWithTools()
325
+ startAppServer()
326
+
327
+ if __name__ == "__main__":
328
+ main()
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ from fastapi import APIRouter
2
+ from .v0_0_1.routes.bas64interpolationRoute import router as b64_router
3
+ from .v0_0_1.routes.sessionRoute import router as session_router
4
+ from .v0_0_1.routes.jqRoute import router as jq_router
5
+ from .v0_0_1.routes.variablesRoute import router as variable_router
6
+ from .v0_0_1.routes.transactionRoute import router as transaction_router
7
+ from .v0_0_1.routes.restapiRoute import router as restapi_router
8
+ from .v0_0_1.routes.healthRouter import router as health_router
9
+
10
+ api_v001 = APIRouter(prefix="/api/v001")
11
+ api_v001.include_router(health_router)
12
+ api_v001.include_router(b64_router)
13
+ api_v001.include_router(session_router)
14
+ api_v001.include_router(jq_router)
15
+ api_v001.include_router(variable_router)
16
+ api_v001.include_router(transaction_router)
17
+ api_v001.include_router(restapi_router)
@@ -0,0 +1,7 @@
1
+ """
2
+ API version 0.0.1 (import-safe package v0_0_1)
3
+
4
+ This file marks the directory as a Python package to ensure reliable imports:
5
+ from .v0_0_1.routes.bas64interpolationRoute import router
6
+ """
7
+ __all__ = []
File without changes
@@ -0,0 +1,57 @@
1
+ """
2
+ Base64 encode/decode routes.
3
+
4
+ Endpoints:
5
+ - POST /base64/encode
6
+ Encode plain text to Base64.
7
+
8
+ - POST /base64/decode
9
+ Decode a Base64-encoded string to plain text.
10
+ """
11
+ from fastapi import APIRouter, HTTPException
12
+ from ....models.bas64interpolationSchema import (
13
+ Base64EncodeIn,
14
+ Base64EncodeOut,
15
+ Base64DecodeIn,
16
+ Base64DecodeOut,
17
+ )
18
+ from ....utils.bas64interpolation import encode_base64, decode_base64
19
+
20
+ # Router for Base64 operations
21
+ router = APIRouter(prefix="/base64", tags=["Base64"])
22
+
23
+ @router.post("/encode", response_model=Base64EncodeOut)
24
+ def base64_encode(payload: Base64EncodeIn):
25
+ """
26
+ Encode plain text to a Base64 string.
27
+
28
+ Request body (Base64EncodeIn):
29
+ - text: Plain text to encode.
30
+
31
+ Returns:
32
+ Base64EncodeOut with 'encoded' containing the Base64 string.
33
+ """
34
+ try:
35
+ base64_encoded_str = encode_base64(text=payload.text)
36
+ response_payload = Base64EncodeOut(encoded=base64_encoded_str)
37
+ return response_payload
38
+ except Exception as e:
39
+ raise HTTPException(status_code=400, detail=str(e))
40
+
41
+ @router.post("/decode", response_model=Base64DecodeOut)
42
+ def base64_decode(payload: Base64DecodeIn):
43
+ """
44
+ Decode a Base64 string to plain text.
45
+
46
+ Request body (Base64DecodeIn):
47
+ - encodedString: Base64-encoded string to decode.
48
+
49
+ Returns:
50
+ Base64DecodeOut with 'text' containing the decoded plain text.
51
+ """
52
+ try:
53
+ base64_decoded_str = decode_base64(b64_text=payload.encodedString)
54
+ response_payload = Base64DecodeOut(text=base64_decoded_str)
55
+ return response_payload
56
+ except Exception as e:
57
+ raise HTTPException(status_code=400, detail=str(e))
@@ -0,0 +1,7 @@
1
+ from fastapi import APIRouter
2
+
3
+ router = APIRouter(tags=["Health"])
4
+
5
+ @router.get("/health")
6
+ def health_env():
7
+ return {"status": "ok"}
@@ -0,0 +1,29 @@
1
+ """
2
+ JQ expression evaluation routes.
3
+
4
+ Endpoints:
5
+ - POST /jq/eval
6
+ Evaluate a JQ expression against supplied JSON input.
7
+ """
8
+ from fastapi import APIRouter, HTTPException
9
+ from ....utils import jqinterpolation
10
+ from ....models.jqSchema import JQExpressionIn, JQExpressionOut
11
+
12
+ router = APIRouter(prefix="/jq", tags=["JQ"])
13
+
14
+ @router.post("/eval", response_model=JQExpressionOut)
15
+ def jq_expression_evaluation(payload: JQExpressionIn):
16
+ """
17
+ Evaluate a JQ expression against provided JSON input.
18
+
19
+ Request body (JQExpressionIn):
20
+ - expression: JQ expression string to evaluate.
21
+ - data: JSON input object/array the expression applies to.
22
+
23
+ Returns:
24
+ JQExpressionOut: Pydantic model with 'result' containing the evaluation output.
25
+ """
26
+ try:
27
+ return JQExpressionOut(result=jqinterpolation.jqinterpolate(expression=payload.expression,json_input=payload.data))
28
+ except Exception as e:
29
+ raise HTTPException(status_code=400, detail=str(e))