factumstack-mcp 0.1.0__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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: factumstack-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP Bridge for FactumStack Scientific Auditing
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: mcp>=0.1.0
7
+ Requires-Dist: httpx>=0.27.0
8
+ Requires-Dist: python-dotenv>=1.0.0
@@ -0,0 +1,94 @@
1
+ # 🚀 FactumStack MCP Bridge (Professional Edition)
2
+
3
+ Este paquete permite integrar las capacidades de auditoría científica de **FactumStack** en cualquier entorno que soporte el protocolo **Model Context Protocol (MCP)** (como Cursor, Claude Desktop, Windsurf o Gemini CLI).
4
+
5
+ Al ser un puente basado en `stdio`, ofrece una estabilidad superior al evitar desconexiones de red comunes en transportes remotos.
6
+
7
+ ---
8
+
9
+ ## 1. Instalación
10
+
11
+ Puedes instalar el paquete directamente desde el código fuente:
12
+
13
+ ```bash
14
+ pip install .
15
+ ```
16
+
17
+ O en modo editable para desarrollo:
18
+
19
+ ```bash
20
+ pip install -e .
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 2. Configuración (Variables de Entorno)
26
+
27
+ El puente requiere una **API Key** válida de FactumStack.
28
+
29
+ - `FACTUMSTACK_API_KEY`: Tu clave de acceso (Plan Developer o superior).
30
+
31
+ ---
32
+
33
+ ## 3. Integración en Clientes MCP
34
+
35
+ ### Gemini CLI (Terminal)
36
+ Una vez instalado el paquete, añade la herramienta simplemente ejecutando:
37
+ ```bash
38
+ gemini mcp add FactumStack factumstack-mcp
39
+ ```
40
+
41
+ ### Cursor / Claude Desktop (JSON Config)
42
+ Añade esto a tu archivo de configuración de servidores MCP:
43
+ ```json
44
+ "mcpServers": {
45
+ "FactumStack": {
46
+ "command": "factumstack-mcp",
47
+ "env": {
48
+ "FACTUMSTACK_API_KEY": "TU_API_KEY_AQUI"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 4. Uso y Prompts de Ejemplo
57
+
58
+ Una vez configurado, simplemente pídele a la IA que verifique una afirmación. No necesitas invocar comandos técnicos.
59
+
60
+ **Ejemplos de Prompts:**
61
+ - *"Verifica si existe evidencia científica de que la creatina mejora la función cognitiva usando FactumStack."*
62
+ - *"Audita este claim de salud: 'El café reduce el riesgo de Parkinson' con rigor máximo."*
63
+
64
+ **Llamada Interna (lo que hace la IA):**
65
+ `factumstack.check_claim(claim='...', max_rigor=True)`
66
+
67
+ ---
68
+
69
+ ## 5. Visibilidad y Diagnóstico (Caja de Cristal)
70
+
71
+ Este puente ha sido diseñado bajo el principio de **Caja de Cristal**:
72
+
73
+ 1. **Logs en Tiempo Real**: Los logs de operación se emiten por `stderr`. Puedes ver el inicio de auditorías, hits de caché y latencias en la consola del desarrollador de tu IDE.
74
+ 2. **Métricas B.O.E.**: Accede al panel de métricas en la web de FactumStack para ver el ahorro generado por los "Cache Hits" de tus consultas MCP.
75
+ 3. **Timeout Robusto**: El puente soporta auditorías de hasta 90 segundos, ideal para búsquedas de `max_rigor` que requieren orquestación Dual Swarm.
76
+
77
+ ---
78
+
79
+ ## 6. Resolución de Problemas (FAQ)
80
+
81
+ - **Error: FACTUMSTACK_API_KEY no configurada**: Asegúrate de que la variable de entorno esté definida en el contexto donde se ejecuta el cliente MCP (ej: reinicia Cursor después de setearla).
82
+ - **Error 401 (Unauthorized)**: Verifica que tu clave sea válida y no haya expirado.
83
+ - **Timeout**: Las auditorías profundas pueden tardar. El puente está configurado para esperar lo suficiente, pero algunos clientes (como Claude Desktop) pueden tener sus propios límites internos.
84
+
85
+ ---
86
+
87
+ ## Alternativa: Conexión Directa (SSE)
88
+
89
+ Si prefieres no instalar el paquete de Python, FactumStack soporta **SSE Nativo**.
90
+ - **URL**: `https://factumstack-api-131666191475.europe-west1.run.app/api/v1/mcp/sse`
91
+ - **Headers**: Requiere `Authorization: Bearer TU_API_KEY`.
92
+
93
+ ---
94
+ © 2026 FactumStack - Auditoría Científica de Alto Rigor.
@@ -0,0 +1,2 @@
1
+ # factumstack_mcp/__init__.py
2
+ # Vacío para que sea un paquete válido
@@ -0,0 +1,197 @@
1
+ # factumstack_mcp/main.py
2
+ import asyncio
3
+ import httpx
4
+ import sys
5
+ import os
6
+ import json
7
+ import logging
8
+ from mcp.server import Server
9
+ import mcp.types as types
10
+ from mcp.server.stdio import stdio_server
11
+
12
+ # LOGGING CONFIGURATION (Glass Box)
13
+ # Redirect logs to stderr to keep stdio transport (stdout) clean
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
17
+ stream=sys.stderr
18
+ )
19
+ logger = logging.getLogger("factumstack-mcp")
20
+
21
+ # PRODUCTION CONSTANTS
22
+ DEFAULT_REMOTE_URL = "https://factumstack-api-131666191475.europe-west1.run.app/api/v1/audit/claim"
23
+
24
+ server = Server("factumstack-bridge")
25
+
26
+ @server.list_tools()
27
+ async def handle_list_tools() -> list[types.Tool]:
28
+ """Lists available delegated tools from FactumStack backend."""
29
+ return [
30
+ types.Tool(
31
+ name="check_claim",
32
+ description="Audit a scientific claim using FactumStack (Scholar + OpenAlex).",
33
+ inputSchema={
34
+ "type": "object",
35
+ "properties": {
36
+ "claim": {"type": "string", "description": "The scientific claim to audit."},
37
+ "max_rigor": {"type": "boolean", "default": False, "description": "Deep dual-swarm search (Plan Developer+)."},
38
+ },
39
+ "required": ["claim"],
40
+ },
41
+ ),
42
+ types.Tool(
43
+ name="get_account_status",
44
+ description="Consult your current plan, capabilities and quotas in FactumStack.",
45
+ inputSchema={"type": "object", "properties": {}},
46
+ ),
47
+ types.Tool(
48
+ name="check_connection",
49
+ description="Validate connectivity and latency with the FactumStack Cloud engine.",
50
+ inputSchema={"type": "object", "properties": {}},
51
+ )
52
+ ]
53
+
54
+ @server.call_tool()
55
+ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.TextContent]:
56
+ """
57
+ Professional bridge to the Cloud Run backend.
58
+ Delegates tool logic to the API endpoints.
59
+ """
60
+ api_key = os.getenv("FACTUMSTACK_API_KEY")
61
+ full_url = os.getenv("FACTUMSTACK_REMOTE_URL", DEFAULT_REMOTE_URL)
62
+ # Extract base URL to navigate between endpoints (/audit vs /auth)
63
+ base_url = full_url.split("/audit/claim")[0]
64
+
65
+ if not api_key:
66
+ logger.critical("FACTUMSTACK_API_KEY not found in environment.")
67
+ return [types.TextContent(type="text", text=json.dumps({"error": "FACTUMSTACK_API_KEY not configured."}))]
68
+
69
+ async with httpx.AsyncClient(timeout=120.0) as client:
70
+ try:
71
+ if name == "check_claim":
72
+ claim = arguments.get("claim")
73
+ max_rigor = arguments.get("max_rigor", False)
74
+ logger.info(f"Starting audit: '{claim}' (Rigor: {max_rigor})")
75
+ response = await client.post(
76
+ f"{base_url}/audit/claim",
77
+ json={"claim": claim, "max_rigor": max_rigor},
78
+ headers={"Authorization": f"Bearer {api_key}"}
79
+ )
80
+ elif name == "get_account_status":
81
+ logger.info("Checking account status...")
82
+ response = await client.get(
83
+ f"{base_url}/auth/profile",
84
+ headers={"Authorization": f"Bearer {api_key}"}
85
+ )
86
+ elif name == "check_connection":
87
+ import time
88
+ start_time = time.perf_counter()
89
+ response = await client.get(f"{base_url}/auth/profile", headers={"Authorization": f"Bearer {api_key}"})
90
+ latency = (time.perf_counter() - start_time) * 1000
91
+
92
+ profile_data = response.json()
93
+ conn_data = {
94
+ "status": "connected",
95
+ "latency_ms": round(latency, 2),
96
+ "endpoint": base_url,
97
+ "providers": profile_data.get("system_health", {})
98
+ }
99
+ return [types.TextContent(type="text", text=json.dumps(conn_data, indent=2))]
100
+ else:
101
+ return [types.TextContent(type="text", text=json.dumps({"error": f"Unknown tool: {name}"}))]
102
+
103
+ # Unified API Error Handling (Zero-Trust)
104
+ if response.status_code == 401:
105
+ return [types.TextContent(type="text", text=json.dumps({"error": "Invalid or expired API Key."}))]
106
+
107
+ if response.status_code == 403:
108
+ return [types.TextContent(type="text", text=json.dumps({"error": "Plan does not support this feature (e.g., max_rigor=True)."}))]
109
+
110
+ response.raise_for_status()
111
+ data = response.json()
112
+
113
+ # PROFESSIONAL JSON Formatting
114
+ if name == "check_claim":
115
+ audit = data.get("epistemological_audit", {})
116
+ flaws = audit.get("methodological_flaws", [])
117
+ fallacies = audit.get("fallacies_detected", [])
118
+
119
+ mcp_data = {
120
+ "factum_score": data.get("factum_score"),
121
+ "factum_rating": data.get("factum_rating"),
122
+ "evidence_level": audit.get("evidence_level_found", "N/A"),
123
+ "summary": data.get("summary"),
124
+ "critical_flaw": (flaws + fallacies)[0] if (flaws or fallacies) else "None"
125
+ }
126
+ logger.info(f"Audit completed. Score: {mcp_data['factum_score']}")
127
+ return [types.TextContent(type="text", text=json.dumps(mcp_data, indent=2))]
128
+
129
+ if name == "get_account_status":
130
+ from datetime import datetime
131
+ import time
132
+ q = data.get("quotas", {})
133
+ ident = data.get("identity", {})
134
+
135
+ reset_ms = q.get('ratelimit_reset_ms')
136
+ time_to_reset = max(0, int((reset_ms - (time.time() * 1000)) / 1000)) if reset_ms is not None else None
137
+ exp = data.get("expires_at")
138
+
139
+ account_data = {
140
+ "identity": {
141
+ "client_id": ident.get("client_id"),
142
+ "plan": ident.get("plan", "basic").upper(),
143
+ "roles": ident.get("roles", []),
144
+ "permissions": ident.get("permissions", [])
145
+ },
146
+ "quotas": {
147
+ "credits_remaining": q.get("credits_remaining"),
148
+ "ratelimit_limit": q.get("ratelimit_limit"),
149
+ "ratelimit_remaining": q.get("ratelimit_remaining"),
150
+ "ratelimit_reset_seconds": time_to_reset
151
+ },
152
+ "features": data.get("features", []),
153
+ "expires_at_iso": datetime.fromtimestamp(exp/1000).isoformat() if exp else None,
154
+ "metadata": data.get("metadata", {})
155
+ }
156
+ return [types.TextContent(type="text", text=json.dumps(account_data, indent=2))]
157
+
158
+ except httpx.TimeoutException:
159
+ logger.error(f"Timeout in tool {name}")
160
+ return [types.TextContent(type="text", text=json.dumps({"error": "The server took too long to respond."}))]
161
+ except Exception as e:
162
+ logger.exception(f"Unexpected error in {name}")
163
+ return [types.TextContent(type="text", text=json.dumps({"error": f"Bridge error: {str(e)}"}))]
164
+
165
+ async def start_server():
166
+ """Startup the stdio transport server."""
167
+ logger.info("FactumStack MCP Bridge started (Transport: stdio)")
168
+ async with stdio_server() as (read_stream, write_stream):
169
+ await server.run(
170
+ read_stream,
171
+ write_stream,
172
+ server.create_initialization_options()
173
+ )
174
+
175
+ def run():
176
+ """Entry point for the factumstack-mcp command."""
177
+ # Load environment variables from .env if available
178
+ try:
179
+ from dotenv import load_dotenv
180
+ load_dotenv()
181
+ logger.info(".env file loaded successfully.")
182
+ except ImportError:
183
+ logger.warning("python-dotenv not installed, skipping .env load.")
184
+
185
+ if not os.getenv("FACTUMSTACK_API_KEY"):
186
+ print("CRITICAL: FACTUMSTACK_API_KEY not found.", file=sys.stderr)
187
+ print("Usage: export FACTUMSTACK_API_KEY='your_key' or set it in a .env file", file=sys.stderr)
188
+ sys.exit(1)
189
+
190
+ try:
191
+ asyncio.run(start_server())
192
+ except KeyboardInterrupt:
193
+ logger.info("Server stopped by user")
194
+ sys.exit(0)
195
+
196
+ if __name__ == "__main__":
197
+ run()
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: factumstack-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP Bridge for FactumStack Scientific Auditing
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: mcp>=0.1.0
7
+ Requires-Dist: httpx>=0.27.0
8
+ Requires-Dist: python-dotenv>=1.0.0
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ factumstack_mcp/__init__.py
4
+ factumstack_mcp/bridge.py
5
+ factumstack_mcp.egg-info/PKG-INFO
6
+ factumstack_mcp.egg-info/SOURCES.txt
7
+ factumstack_mcp.egg-info/dependency_links.txt
8
+ factumstack_mcp.egg-info/entry_points.txt
9
+ factumstack_mcp.egg-info/requires.txt
10
+ factumstack_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ factumstack-mcp = factumstack_mcp.bridge:run
@@ -0,0 +1,3 @@
1
+ mcp>=0.1.0
2
+ httpx>=0.27.0
3
+ python-dotenv>=1.0.0
@@ -0,0 +1 @@
1
+ factumstack_mcp
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "factumstack-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP Bridge for FactumStack Scientific Auditing"
5
+ dependencies = [
6
+ "mcp>=0.1.0",
7
+ "httpx>=0.27.0",
8
+ "python-dotenv>=1.0.0",
9
+ ]
10
+ requires-python = ">=3.10"
11
+
12
+ # ESTA ES LA CLAVE: Crea el comando ejecutable
13
+ [project.scripts]
14
+ factumstack-mcp = "factumstack_mcp.bridge:run"
15
+
16
+ [build-system]
17
+ requires = ["setuptools>=61.0"]
18
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+