restapi-mcp-server 0.0.1__tar.gz
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.
- restapi_mcp_server-0.0.1/PKG-INFO +96 -0
- restapi_mcp_server-0.0.1/README.md +80 -0
- restapi_mcp_server-0.0.1/pyproject.toml +35 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/__init__.py +4 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/__main__.py +328 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/router.py +17 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/__init__.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/bas64interpolationRoute.py +57 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/healthRouter.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/jqRoute.py +29 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/restapiRoute.py +252 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/sessionRoute.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/transactionRoute.py +46 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/api/v0_0_1/routes/variablesRoute.py +87 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/constants/COMMON.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/constants/RESTAPI.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/constants/__init__.py +4 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/__init__.py +9 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/bas64interpolationSchema.py +35 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/jqSchema.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/restapiSchema.py +101 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/sessionSchema.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/transacationSchema.py +42 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/models/variablesSchema.py +65 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/services/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/services/restapi.py +499 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/services/transactions.py +142 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/services/variablesInterpolation.py +112 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/router.py +17 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/__init__.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/bas64interpolationRoute.py +57 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/healthRouter.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/jqRoute.py +29 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/restapiRoute.py +34 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/sessionRoute.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/transactionRoute.py +46 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/api/v0_0_1/routes/variablesRoute.py +87 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/constants/COMMON.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/constants/RESTAPI.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/constants/__init__.py +4 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/__init__.py +9 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/bas64interpolationSchema.py +35 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/jqSchema.py +25 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/restapiSchema.py +54 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/sessionSchema.py +7 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/transacationSchema.py +42 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/models/variablesSchema.py +65 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/services/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/services/restapi.py +385 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/services/transactions.py +142 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/services/variablesInterpolation.py +112 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/bas64interpolation.py +111 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/env.py +93 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/idgen.py +23 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/interpolation.py +60 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/jqinterpolation.py +70 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/jsoncodec.py +77 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/logger.py +38 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/persist.py +146 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/src/utils/restapi.py +233 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/__init__.py +0 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/bas64interpolation.py +111 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/env.py +93 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/idgen.py +23 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/interpolation.py +60 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/jqinterpolation.py +70 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/jsoncodec.py +77 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/logger.py +38 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/persist.py +146 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server/src/utils/restapi.py +269 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/PKG-INFO +96 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/SOURCES.txt +82 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/dependency_links.txt +1 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/entry_points.txt +2 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/requires.txt +9 -0
- restapi_mcp_server-0.0.1/restapi_mcp_server.egg-info/top_level.txt +1 -0
- restapi_mcp_server-0.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: restapi-mcp-server
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: RestAPI MCP Server - SSE
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastapi>=0.128.0
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: jq>=1.10.0
|
|
10
|
+
Requires-Dist: mcp>=1.25.0
|
|
11
|
+
Requires-Dist: pandas>=2.3.3
|
|
12
|
+
Requires-Dist: pydantic>=2.12.5
|
|
13
|
+
Requires-Dist: python-multipart>=0.0.20
|
|
14
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
15
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
16
|
+
|
|
17
|
+
# RestAPI MCP Server
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
- This project will install `MCP - Model Context Protocol Server`, that provides configured REST API's as context to LLM's.
|
|
21
|
+
- Using this we can enable LLMs to do RestAPI's using prompts.
|
|
22
|
+
- Currently we support HTTP API Call's `GET/PUT/POST/PATCH/DELETE`.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
- Install package
|
|
26
|
+
```bash
|
|
27
|
+
source start.sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Claud Desktop
|
|
31
|
+
- Configuration details for Claud Desktop
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"restapi_mcp_server":{
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["mcp-remote","<<IP:765/sse>>","--allow-http"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
- Cline
|
|
43
|
+
```json
|
|
44
|
+
"DEMO": {
|
|
45
|
+
"autoApprove": [
|
|
46
|
+
"createSession",
|
|
47
|
+
"createEnviromentVariables",
|
|
48
|
+
"listAllEnviromentVariables",
|
|
49
|
+
"listSpecificEnvironmentVariable",
|
|
50
|
+
"createRestAPICall"
|
|
51
|
+
],
|
|
52
|
+
"disabled": false,
|
|
53
|
+
"timeout": 60,
|
|
54
|
+
"type": "stdio",
|
|
55
|
+
"command": "npx",
|
|
56
|
+
"args": [
|
|
57
|
+
"mcp-remote",
|
|
58
|
+
"http://<<IP>>:8765/sse",
|
|
59
|
+
"--allow-http"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Configuration
|
|
65
|
+
- List of available environment variables
|
|
66
|
+
- `DEBUG`: Enable debug logging (optional default is False)
|
|
67
|
+
|
|
68
|
+
## Contributing
|
|
69
|
+
Contributions are welcome.
|
|
70
|
+
Please feel free to submit a Pull Request.
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
This project is licensed under the terms of the MIT license.
|
|
74
|
+
|
|
75
|
+
## Github Stars
|
|
76
|
+
[](https://star-history.com/#rahgadda/restapi_mcp_server&Date)
|
|
77
|
+
|
|
78
|
+
## Appendix
|
|
79
|
+
### UV
|
|
80
|
+
```bash
|
|
81
|
+
mkdir -m777 restapi_mcp_server
|
|
82
|
+
cd restapi_mcp_server
|
|
83
|
+
uv init
|
|
84
|
+
uv add mcp[cli] pydantic python-dotenv requests
|
|
85
|
+
uv add --dev twine setuptools
|
|
86
|
+
uv sync
|
|
87
|
+
uv run restapi_mcp_server
|
|
88
|
+
uv build
|
|
89
|
+
pip install --force-reinstall --no-deps .\dist\restapi_mcp_server-*fileversion*.whl
|
|
90
|
+
export TWINE_USERNAME="rahgadda"
|
|
91
|
+
export TWINE_USERNAME="<<API Key>>"
|
|
92
|
+
uv run twine upload --verbose dist/*
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Reference
|
|
96
|
+
- [UV Overview](https://www.youtube.com/watch?v=WKc2BdgmGZE)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# RestAPI MCP Server
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
- This project will install `MCP - Model Context Protocol Server`, that provides configured REST API's as context to LLM's.
|
|
5
|
+
- Using this we can enable LLMs to do RestAPI's using prompts.
|
|
6
|
+
- Currently we support HTTP API Call's `GET/PUT/POST/PATCH/DELETE`.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
- Install package
|
|
10
|
+
```bash
|
|
11
|
+
source start.sh
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Claud Desktop
|
|
15
|
+
- Configuration details for Claud Desktop
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"restapi_mcp_server":{
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["mcp-remote","<<IP:765/sse>>","--allow-http"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
- Cline
|
|
27
|
+
```json
|
|
28
|
+
"DEMO": {
|
|
29
|
+
"autoApprove": [
|
|
30
|
+
"createSession",
|
|
31
|
+
"createEnviromentVariables",
|
|
32
|
+
"listAllEnviromentVariables",
|
|
33
|
+
"listSpecificEnvironmentVariable",
|
|
34
|
+
"createRestAPICall"
|
|
35
|
+
],
|
|
36
|
+
"disabled": false,
|
|
37
|
+
"timeout": 60,
|
|
38
|
+
"type": "stdio",
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": [
|
|
41
|
+
"mcp-remote",
|
|
42
|
+
"http://<<IP>>:8765/sse",
|
|
43
|
+
"--allow-http"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Configuration
|
|
49
|
+
- List of available environment variables
|
|
50
|
+
- `DEBUG`: Enable debug logging (optional default is False)
|
|
51
|
+
|
|
52
|
+
## Contributing
|
|
53
|
+
Contributions are welcome.
|
|
54
|
+
Please feel free to submit a Pull Request.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
This project is licensed under the terms of the MIT license.
|
|
58
|
+
|
|
59
|
+
## Github Stars
|
|
60
|
+
[](https://star-history.com/#rahgadda/restapi_mcp_server&Date)
|
|
61
|
+
|
|
62
|
+
## Appendix
|
|
63
|
+
### UV
|
|
64
|
+
```bash
|
|
65
|
+
mkdir -m777 restapi_mcp_server
|
|
66
|
+
cd restapi_mcp_server
|
|
67
|
+
uv init
|
|
68
|
+
uv add mcp[cli] pydantic python-dotenv requests
|
|
69
|
+
uv add --dev twine setuptools
|
|
70
|
+
uv sync
|
|
71
|
+
uv run restapi_mcp_server
|
|
72
|
+
uv build
|
|
73
|
+
pip install --force-reinstall --no-deps .\dist\restapi_mcp_server-*fileversion*.whl
|
|
74
|
+
export TWINE_USERNAME="rahgadda"
|
|
75
|
+
export TWINE_USERNAME="<<API Key>>"
|
|
76
|
+
uv run twine upload --verbose dist/*
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Reference
|
|
80
|
+
- [UV Overview](https://www.youtube.com/watch?v=WKc2BdgmGZE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "restapi-mcp-server"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "RestAPI MCP Server - SSE"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"fastapi>=0.128.0",
|
|
9
|
+
"httpx>=0.28.1",
|
|
10
|
+
"jq>=1.10.0",
|
|
11
|
+
"mcp>=1.25.0",
|
|
12
|
+
"pandas>=2.3.3",
|
|
13
|
+
"pydantic>=2.12.5",
|
|
14
|
+
"python-multipart>=0.0.20",
|
|
15
|
+
"pyyaml>=6.0.3",
|
|
16
|
+
"uvicorn>=0.40.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = [
|
|
21
|
+
"setuptools>=80.9.0",
|
|
22
|
+
"twine>=6.2.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
restapi_mcp_server = "restapi_mcp_server.__main__:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["setuptools>=42", "wheel"]
|
|
30
|
+
build-backend = "setuptools.build_meta"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
# Explicitly include only our package and exclude the storage folder
|
|
34
|
+
include = ["restapi_mcp_server*"]
|
|
35
|
+
exclude = ["storage*"]
|
|
@@ -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)
|
|
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,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))
|