rationalbloks-mcp 0.1.14__tar.gz → 0.1.19__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.
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/PKG-INFO +1 -1
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/pyproject.toml +1 -1
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/src/rationalbloks_mcp/__init__.py +9 -7
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/src/rationalbloks_mcp/client.py +17 -7
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/src/rationalbloks_mcp/server.py +71 -36
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/src/rationalbloks_mcp/tools.py +17 -0
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/.gitignore +0 -0
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/LICENSE +0 -0
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/README.md +0 -0
- {rationalbloks_mcp-0.1.14 → rationalbloks_mcp-0.1.19}/src/rationalbloks_mcp/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rationalbloks-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
4
4
|
Summary: Enterprise-grade MCP Server for RationalBloks - Connect AI agents to your backend projects
|
|
5
5
|
Project-URL: Homepage, https://rationalbloks.com
|
|
6
6
|
Project-URL: Documentation, https://rationalbloks.com/docs/mcp
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rationalbloks-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.19"
|
|
8
8
|
description = "Enterprise-grade MCP Server for RationalBloks - Connect AI agents to your backend projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Proprietary"}
|
|
@@ -26,15 +26,17 @@ from .server import RationalBloksMCPServer
|
|
|
26
26
|
from .client import RationalBloksClient
|
|
27
27
|
from .tools import TOOLS
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# VERSION - Single Source of Truth (Chain Mantra)
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# Read from pyproject.toml via importlib.metadata
|
|
33
|
+
# If this fails, the package is not installed correctly - fail immediately
|
|
34
|
+
# NO FALLBACKS - there is only ONE correct path
|
|
35
|
+
from importlib.metadata import version as _get_version
|
|
36
|
+
__version__ = _get_version("rationalbloks-mcp")
|
|
35
37
|
|
|
36
38
|
__author__ = "RationalBloks"
|
|
37
|
-
__all__ = ["RationalBloksMCPServer", "RationalBloksClient", "TOOLS", "main"]
|
|
39
|
+
__all__ = ["RationalBloksMCPServer", "RationalBloksClient", "TOOLS", "main", "__version__"]
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def main() -> None:
|
|
@@ -21,12 +21,19 @@ import os
|
|
|
21
21
|
import time
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
# ============================================================================
|
|
25
|
+
# VERSION - Import from package root (Chain Mantra: Single Source of Truth)
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# Deferred import to avoid circular dependency during module loading
|
|
28
|
+
# The version is set after the module is fully loaded via __init__.py
|
|
29
|
+
_version_cache: str | None = None
|
|
30
|
+
|
|
31
|
+
def _get_version() -> str:
|
|
32
|
+
global _version_cache
|
|
33
|
+
if _version_cache is None:
|
|
34
|
+
from importlib.metadata import version
|
|
35
|
+
_version_cache = version("rationalbloks-mcp")
|
|
36
|
+
return _version_cache
|
|
30
37
|
|
|
31
38
|
# Default configuration (can be overridden via environment variables)
|
|
32
39
|
GATEWAY_URL = os.environ.get("RATIONALBLOKS_BASE_URL", "https://logicblok.rationalbloks.com")
|
|
@@ -34,6 +41,9 @@ REQUEST_TIMEOUT = float(os.environ.get("RATIONALBLOKS_TIMEOUT", "30"))
|
|
|
34
41
|
MAX_RETRIES = 3
|
|
35
42
|
RETRY_DELAY = 1.0 # Base delay in seconds, exponentially increases
|
|
36
43
|
|
|
44
|
+
# Public API
|
|
45
|
+
__all__ = ["RationalBloksClient"]
|
|
46
|
+
|
|
37
47
|
|
|
38
48
|
class RationalBloksClient:
|
|
39
49
|
# HTTP client for MCP Gateway communication
|
|
@@ -59,7 +69,7 @@ class RationalBloksClient:
|
|
|
59
69
|
headers={
|
|
60
70
|
"Authorization": f"Bearer {api_key}",
|
|
61
71
|
"Content-Type": "application/json",
|
|
62
|
-
"User-Agent": f"rationalbloks-mcp/{
|
|
72
|
+
"User-Agent": f"rationalbloks-mcp/{_get_version()}"
|
|
63
73
|
},
|
|
64
74
|
timeout=self.timeout,
|
|
65
75
|
follow_redirects=True
|
|
@@ -38,19 +38,23 @@ from mcp.types import (
|
|
|
38
38
|
PromptArgument,
|
|
39
39
|
PromptMessage,
|
|
40
40
|
GetPromptResult,
|
|
41
|
-
Resource
|
|
41
|
+
Resource,
|
|
42
|
+
Icon
|
|
42
43
|
)
|
|
43
44
|
from starlette.requests import Request
|
|
44
45
|
|
|
45
46
|
from .client import RationalBloksClient
|
|
46
47
|
from .tools import TOOLS
|
|
47
48
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# VERSION - Import from package root (Chain Mantra: Single Source of Truth)
|
|
51
|
+
# ============================================================================
|
|
52
|
+
# Deferred import to avoid circular dependency during module loading
|
|
53
|
+
from importlib.metadata import version as _get_pkg_version
|
|
54
|
+
__version__ = _get_pkg_version("rationalbloks-mcp")
|
|
55
|
+
|
|
56
|
+
# Public API
|
|
57
|
+
__all__ = ["RationalBloksMCPServer"]
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
# ============================================================================
|
|
@@ -159,6 +163,10 @@ class RationalBloksMCPServer:
|
|
|
159
163
|
version=__version__,
|
|
160
164
|
instructions="RationalBloks MCP Server - Enterprise Backend-as-a-Service for AI agents. Build production APIs from JSON schemas.",
|
|
161
165
|
website_url="https://rationalbloks.com",
|
|
166
|
+
icons=[
|
|
167
|
+
Icon(src="https://rationalbloks.com/logo.svg", mimeType="image/svg+xml"),
|
|
168
|
+
Icon(src="https://rationalbloks.com/logo.png", mimeType="image/png", sizes=["128x128"]),
|
|
169
|
+
],
|
|
162
170
|
)
|
|
163
171
|
self._setup_handlers()
|
|
164
172
|
|
|
@@ -346,19 +354,22 @@ class RationalBloksMCPServer:
|
|
|
346
354
|
description=f"Deployment status and metadata for {project_name}",
|
|
347
355
|
mimeType="application/json"
|
|
348
356
|
))
|
|
349
|
-
except Exception:
|
|
350
|
-
#
|
|
351
|
-
#
|
|
352
|
-
|
|
357
|
+
except Exception as e:
|
|
358
|
+
# Log warning but continue - static resources are still available
|
|
359
|
+
# Chain Mantra: Dynamic resources are optional, but failures must be visible
|
|
360
|
+
print(f"[rationalbloks-mcp] Warning: Failed to list dynamic resources: {e}", file=sys.stderr)
|
|
353
361
|
|
|
354
362
|
return resources
|
|
355
363
|
|
|
356
364
|
@self.server.read_resource()
|
|
357
|
-
async def read_resource(uri
|
|
365
|
+
async def read_resource(uri) -> str:
|
|
358
366
|
# Read a specific resource by URI
|
|
359
367
|
# Handles both static docs and dynamic project resources
|
|
360
368
|
# WHY: Provides AI agents access to docs and project schemas
|
|
361
369
|
|
|
370
|
+
# Convert AnyUrl to string for comparison
|
|
371
|
+
uri_str = str(uri)
|
|
372
|
+
|
|
362
373
|
# Static documentation resources - no auth required
|
|
363
374
|
static_docs = {
|
|
364
375
|
"rationalbloks://docs/getting-started": DOCS_GETTING_STARTED,
|
|
@@ -366,8 +377,8 @@ class RationalBloksMCPServer:
|
|
|
366
377
|
"rationalbloks://docs/api-reference": DOCS_API_REFERENCE,
|
|
367
378
|
}
|
|
368
379
|
|
|
369
|
-
if
|
|
370
|
-
return static_docs[
|
|
380
|
+
if uri_str in static_docs:
|
|
381
|
+
return static_docs[uri_str]
|
|
371
382
|
|
|
372
383
|
# Dynamic project resources - require authentication
|
|
373
384
|
client = self._get_client_for_request()
|
|
@@ -375,12 +386,12 @@ class RationalBloksMCPServer:
|
|
|
375
386
|
raise ValueError("Authentication required to read project resources")
|
|
376
387
|
|
|
377
388
|
# Parse URI: rationalbloks://project/{id}/{type}
|
|
378
|
-
if not
|
|
379
|
-
raise ValueError(f"Invalid URI format: {
|
|
389
|
+
if not uri_str.startswith("rationalbloks://project/"):
|
|
390
|
+
raise ValueError(f"Invalid URI format: {uri_str}")
|
|
380
391
|
|
|
381
|
-
parts =
|
|
392
|
+
parts = uri_str.replace("rationalbloks://project/", "").split("/")
|
|
382
393
|
if len(parts) != 2:
|
|
383
|
-
raise ValueError(f"Invalid URI format: {
|
|
394
|
+
raise ValueError(f"Invalid URI format: {uri_str}")
|
|
384
395
|
|
|
385
396
|
project_id, resource_type = parts
|
|
386
397
|
|
|
@@ -399,9 +410,14 @@ class RationalBloksMCPServer:
|
|
|
399
410
|
@self.server.call_tool()
|
|
400
411
|
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
401
412
|
# Execute a tool with provided arguments
|
|
402
|
-
# Single code path: get client → execute → format response
|
|
413
|
+
# Single code path: validate → get client → execute → format response
|
|
403
414
|
# WHY: Core MCP operation - all tool invocations flow through here
|
|
404
415
|
|
|
416
|
+
# Validate tool name exists
|
|
417
|
+
valid_tools = [t["name"] for t in TOOLS]
|
|
418
|
+
if name not in valid_tools:
|
|
419
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
420
|
+
|
|
405
421
|
try:
|
|
406
422
|
client = self._get_client_for_request()
|
|
407
423
|
|
|
@@ -429,24 +445,33 @@ class RationalBloksMCPServer:
|
|
|
429
445
|
# Get the appropriate client for the current request
|
|
430
446
|
# STDIO mode: Returns pre-configured client with environment API key
|
|
431
447
|
# HTTP mode: Extracts API key from Authorization Bearer header per-request
|
|
432
|
-
#
|
|
448
|
+
# Chain Mantra: Single path through code, explicit None returns
|
|
433
449
|
|
|
450
|
+
# STDIO mode - return pre-configured client
|
|
434
451
|
if not self.http_mode:
|
|
435
452
|
return self.client
|
|
436
453
|
|
|
437
454
|
# HTTP mode - extract API key from Authorization: Bearer header
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
455
|
+
BEARER_PREFIX = "Bearer "
|
|
456
|
+
|
|
457
|
+
# Get request context (may not exist if called outside request)
|
|
458
|
+
ctx = getattr(self.server, 'request_context', None)
|
|
459
|
+
if ctx is None:
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
request = getattr(ctx, 'request', None)
|
|
463
|
+
if request is None or not isinstance(request, Request):
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
auth_header = request.headers.get("authorization", "")
|
|
467
|
+
if not auth_header.startswith(BEARER_PREFIX):
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
api_key = auth_header[len(BEARER_PREFIX):]
|
|
471
|
+
if not api_key.startswith("rb_sk_"):
|
|
472
|
+
return None
|
|
448
473
|
|
|
449
|
-
return
|
|
474
|
+
return RationalBloksClient(api_key)
|
|
450
475
|
|
|
451
476
|
def _get_init_options(self) -> InitializationOptions:
|
|
452
477
|
# Get MCP initialization options for STDIO transport
|
|
@@ -544,11 +569,13 @@ class RationalBloksMCPServer:
|
|
|
544
569
|
"configSchema": {
|
|
545
570
|
"type": "object",
|
|
546
571
|
"title": "RationalBloks Configuration",
|
|
572
|
+
"required": [], # All properties are optional - Smithery quality score
|
|
547
573
|
"properties": {
|
|
548
574
|
"apiKey": {
|
|
549
575
|
"type": "string",
|
|
550
576
|
"title": "API Key",
|
|
551
|
-
"description": "Your RationalBloks API key (get it from https://rationalbloks.com/settings)",
|
|
577
|
+
"description": "Your RationalBloks API key (get it from https://rationalbloks.com/settings). Optional for browsing documentation.",
|
|
578
|
+
"default": "",
|
|
552
579
|
"x-from": {"header": "authorization"}
|
|
553
580
|
},
|
|
554
581
|
"baseUrl": {
|
|
@@ -570,8 +597,7 @@ class RationalBloksMCPServer:
|
|
|
570
597
|
"default": "INFO",
|
|
571
598
|
"enum": ["DEBUG", "INFO", "WARNING", "ERROR"]
|
|
572
599
|
}
|
|
573
|
-
}
|
|
574
|
-
"required": ["apiKey"]
|
|
600
|
+
}
|
|
575
601
|
}
|
|
576
602
|
})
|
|
577
603
|
|
|
@@ -599,12 +625,18 @@ class RationalBloksMCPServer:
|
|
|
599
625
|
yield
|
|
600
626
|
|
|
601
627
|
# Build Starlette ASGI application
|
|
628
|
+
# Multiple paths for MCP endpoint compatibility:
|
|
629
|
+
# - /sse: SSE endpoint for Smithery and cloud clients (documented URL)
|
|
630
|
+
# - /mcp: Alternative path for clarity
|
|
631
|
+
# - /: Root fallback for direct connections
|
|
602
632
|
app = Starlette(
|
|
603
633
|
debug=False,
|
|
604
634
|
routes=[
|
|
605
635
|
Route("/.well-known/mcp/server-card.json", endpoint=server_card, methods=["GET"]),
|
|
606
636
|
Route("/health", endpoint=health, methods=["GET"]),
|
|
607
|
-
Mount("/", app=handle_streamable),
|
|
637
|
+
Mount("/sse", app=handle_streamable), # Primary SSE endpoint (matches frontend docs)
|
|
638
|
+
Mount("/mcp", app=handle_streamable), # Alternative MCP path
|
|
639
|
+
Mount("/", app=handle_streamable), # Root fallback for direct connections
|
|
608
640
|
],
|
|
609
641
|
lifespan=lifespan,
|
|
610
642
|
)
|
|
@@ -624,6 +656,9 @@ class RationalBloksMCPServer:
|
|
|
624
656
|
host = os.environ.get("HOST", "0.0.0.0")
|
|
625
657
|
|
|
626
658
|
print(f"[rationalbloks-mcp] Streamable HTTP server starting on {host}:{port}", file=sys.stderr)
|
|
627
|
-
print(f"[rationalbloks-mcp] MCP
|
|
659
|
+
print(f"[rationalbloks-mcp] MCP endpoints:", file=sys.stderr)
|
|
660
|
+
print(f"[rationalbloks-mcp] - http://{host}:{port}/sse (primary)", file=sys.stderr)
|
|
661
|
+
print(f"[rationalbloks-mcp] - http://{host}:{port}/mcp (alternative)", file=sys.stderr)
|
|
662
|
+
print(f"[rationalbloks-mcp] - http://{host}:{port}/ (root fallback)", file=sys.stderr)
|
|
628
663
|
|
|
629
664
|
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
# - Write Operations (7): Create, update, deploy, delete
|
|
18
18
|
# ============================================================================
|
|
19
19
|
|
|
20
|
+
# Public API
|
|
21
|
+
__all__ = ["TOOLS"]
|
|
22
|
+
|
|
20
23
|
TOOLS = [
|
|
21
24
|
# =========================================================================
|
|
22
25
|
# READ TOOLS (11 total) - All readOnlyHint=True
|
|
@@ -45,6 +48,7 @@ TOOLS = [
|
|
|
45
48
|
"description": "Get detailed information about a specific project",
|
|
46
49
|
"inputSchema": {
|
|
47
50
|
"type": "object",
|
|
51
|
+
"description": "Specify the project to retrieve",
|
|
48
52
|
"properties": {
|
|
49
53
|
"project_id": {
|
|
50
54
|
"type": "string",
|
|
@@ -67,6 +71,7 @@ TOOLS = [
|
|
|
67
71
|
"description": "Get the JSON schema definition of a project",
|
|
68
72
|
"inputSchema": {
|
|
69
73
|
"type": "object",
|
|
74
|
+
"description": "Specify the project whose schema to retrieve",
|
|
70
75
|
"properties": {
|
|
71
76
|
"project_id": {
|
|
72
77
|
"type": "string",
|
|
@@ -107,6 +112,7 @@ TOOLS = [
|
|
|
107
112
|
"description": "Check the status of a deployment job",
|
|
108
113
|
"inputSchema": {
|
|
109
114
|
"type": "object",
|
|
115
|
+
"description": "Specify the job to check",
|
|
110
116
|
"properties": {
|
|
111
117
|
"job_id": {
|
|
112
118
|
"type": "string",
|
|
@@ -129,6 +135,7 @@ TOOLS = [
|
|
|
129
135
|
"description": "Get detailed project info including deployment status and resource usage",
|
|
130
136
|
"inputSchema": {
|
|
131
137
|
"type": "object",
|
|
138
|
+
"description": "Specify the project to get info for",
|
|
132
139
|
"properties": {
|
|
133
140
|
"project_id": {
|
|
134
141
|
"type": "string",
|
|
@@ -151,6 +158,7 @@ TOOLS = [
|
|
|
151
158
|
"description": "Get the deployment and version history (git commits) for a project",
|
|
152
159
|
"inputSchema": {
|
|
153
160
|
"type": "object",
|
|
161
|
+
"description": "Specify the project to get history for",
|
|
154
162
|
"properties": {
|
|
155
163
|
"project_id": {
|
|
156
164
|
"type": "string",
|
|
@@ -209,6 +217,7 @@ TOOLS = [
|
|
|
209
217
|
"description": "Get resource usage metrics (CPU, memory) for a project",
|
|
210
218
|
"inputSchema": {
|
|
211
219
|
"type": "object",
|
|
220
|
+
"description": "Specify the project to get usage metrics for",
|
|
212
221
|
"properties": {
|
|
213
222
|
"project_id": {
|
|
214
223
|
"type": "string",
|
|
@@ -231,6 +240,7 @@ TOOLS = [
|
|
|
231
240
|
"description": "Get the schema as it was at a specific version/commit",
|
|
232
241
|
"inputSchema": {
|
|
233
242
|
"type": "object",
|
|
243
|
+
"description": "Specify the project and version to retrieve schema for",
|
|
234
244
|
"properties": {
|
|
235
245
|
"project_id": {
|
|
236
246
|
"type": "string",
|
|
@@ -261,6 +271,7 @@ TOOLS = [
|
|
|
261
271
|
"description": "Create a new RationalBloks project from a JSON schema",
|
|
262
272
|
"inputSchema": {
|
|
263
273
|
"type": "object",
|
|
274
|
+
"description": "Project name, schema definition, and optional description",
|
|
264
275
|
"properties": {
|
|
265
276
|
"name": {
|
|
266
277
|
"type": "string",
|
|
@@ -291,6 +302,7 @@ TOOLS = [
|
|
|
291
302
|
"description": "Update a project's schema (saves to database, does NOT deploy)",
|
|
292
303
|
"inputSchema": {
|
|
293
304
|
"type": "object",
|
|
305
|
+
"description": "Project to update and new schema definition",
|
|
294
306
|
"properties": {
|
|
295
307
|
"project_id": {
|
|
296
308
|
"type": "string",
|
|
@@ -317,6 +329,7 @@ TOOLS = [
|
|
|
317
329
|
"description": "Deploy a project to the staging environment",
|
|
318
330
|
"inputSchema": {
|
|
319
331
|
"type": "object",
|
|
332
|
+
"description": "Specify the project to deploy to staging",
|
|
320
333
|
"properties": {
|
|
321
334
|
"project_id": {
|
|
322
335
|
"type": "string",
|
|
@@ -339,6 +352,7 @@ TOOLS = [
|
|
|
339
352
|
"description": "Promote staging to production (requires paid plan)",
|
|
340
353
|
"inputSchema": {
|
|
341
354
|
"type": "object",
|
|
355
|
+
"description": "Specify the project to deploy to production",
|
|
342
356
|
"properties": {
|
|
343
357
|
"project_id": {
|
|
344
358
|
"type": "string",
|
|
@@ -361,6 +375,7 @@ TOOLS = [
|
|
|
361
375
|
"description": "Delete a project (removes GitHub repo, K8s deployments, and database)",
|
|
362
376
|
"inputSchema": {
|
|
363
377
|
"type": "object",
|
|
378
|
+
"description": "Specify the project to permanently delete",
|
|
364
379
|
"properties": {
|
|
365
380
|
"project_id": {
|
|
366
381
|
"type": "string",
|
|
@@ -383,6 +398,7 @@ TOOLS = [
|
|
|
383
398
|
"description": "Rollback a project to a previous version",
|
|
384
399
|
"inputSchema": {
|
|
385
400
|
"type": "object",
|
|
401
|
+
"description": "Project, version, and environment for rollback",
|
|
386
402
|
"properties": {
|
|
387
403
|
"project_id": {
|
|
388
404
|
"type": "string",
|
|
@@ -413,6 +429,7 @@ TOOLS = [
|
|
|
413
429
|
"description": "Rename a project (changes display name, not project_code)",
|
|
414
430
|
"inputSchema": {
|
|
415
431
|
"type": "object",
|
|
432
|
+
"description": "Project to rename and new display name",
|
|
416
433
|
"properties": {
|
|
417
434
|
"project_id": {
|
|
418
435
|
"type": "string",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|