diff --git a/Dockerfile 2 b/Dockerfile 2
new file mode 100644
index 0000000..df34608
--- /dev/null
+++ b/Dockerfile 2
@@ -0,0 +1,23 @@
+FROM python:3.12-slim
+
+# Set working directory
+WORKDIR /app
+
+# Copy requirements first for better caching
+COPY requirements.txt .
+
+# Install dependencies
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy the application code
+COPY biel_mcp_server.py .
+
+# Expose the hardcoded port
+EXPOSE 7832
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:7832/sse || exit 1
+
+# Run the server
+CMD ["python", "biel_mcp_server.py"]
\ No newline at end of file
diff --git a/LICENSE 2.md b/LICENSE 2.md
new file mode 100644
index 0000000..0d3e391
--- /dev/null
+++ b/LICENSE 2.md
@@ -0,0 +1,20 @@
+Copyright (c) 2025 TechDocs Studio
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README 2.md b/README 2.md
new file mode 100644
index 0000000..df4fc80
--- /dev/null
+++ b/README 2.md
@@ -0,0 +1,87 @@
+
+
+
+
+
+
Biel.ai MCP Server
+
Connect your IDE to your product docs
+
+
+[](https://archestra.ai/mcp-catalog/techdocsstudio__biel-mcp)
+
+
+Give AI tools like Cursor, VS Code, and Claude Desktop access to your company's product knowledge through the [Biel.ai platform](https://biel.ai).
+
+Biel.ai provides a hosted Retrieval-Augmented Generation (RAG) layer that makes your documentation searchable and useful to AI tools. This enables smarter completions, accurate technical answers, and context-aware suggestions—directly in your IDE or chat environment.
+
+
+
+When AI tools can read your product documentation, they become **significantly** more helpful—generating more accurate code completions, answering technical questions with context, and guiding developers with real-time product knowledge.
+
+
+> **Note:** Requires a Biel.ai account and project setup. **[Start your free 15-day trial](https://app.biel.ai/accounts/signup/)**.
+
+
+
+## Getting started
+
+### 1. Get your MCP configuration
+
+```json
+{
+ "mcpServers": {
+ "biel-ai": {
+ "description": "Query your product's documentation, APIs, and knowledge base.",
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "https://mcp.biel.ai/sse?project_slug=YOUR_PROJECT_SLUG&domain=https://your-docs-domain.com"
+ ]
+ }
+ }
+}
+```
+
+**Required:** `project_slug` and `domain`
+**Optional:** `api_key` (only needed for private projects)
+
+### 2. Add to your AI tool
+
+* **Cursor**: **Settings** → **Tools & Integrations* → **New MCP server**.
+* **Claude Desktop**: Edit `claude_desktop_config.json`
+* **VS Code**: Install **MCP extension**.
+
+### 3. Start asking questions
+
+```
+Can you check in biel_ai what the auth headers are for the /users endpoint?
+```
+
+## Self-hosting (Optional)
+
+For advanced users who prefer to run their own MCP server instance:
+
+### Local development
+```bash
+# Clone and run locally
+git clone https://github.com/techdocsStudio/biel-mcp
+cd biel-mcp
+pip install -r requirements.txt
+python biel_mcp_server.py
+```
+
+### Docker deployment
+```bash
+# Docker Compose (recommended)
+docker-compose up -d --build
+
+# Or Docker directly
+docker build -t biel-mcp .
+docker run -d -p 7832:7832 biel-mcp
+```
+
+## Support
+
+- **Issues**: [GitHub Issues](https://github.com/techdocsStudio/biel-mcp/issues)
+- **Contact**: [support@biel.ai](mailto:support@biel.ai)
+- **Custom Demo**: [Book a demo](https://biel.ai/contact)
diff --git a/README.md b/README.md
index 3d25fe3..f8b139a 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ When AI tools can read your product documentation, they become **significantly**
## Getting started
+[](https://archestra.ai/mcp-catalog/techdocsstudio__biel-mcp)
### 1. Get your MCP configuration
diff --git a/biel_mcp_server 2.py b/biel_mcp_server 2.py
new file mode 100644
index 0000000..9911b12
--- /dev/null
+++ b/biel_mcp_server 2.py
@@ -0,0 +1,406 @@
+"""
+MCP Server for Biel.ai
+Remote MCP server accessible via HTTP
+Allows querying your AI from editors like Cursor via MCP over HTTP
+"""
+
+import asyncio
+import json
+import logging
+from typing import Dict, Optional, Any
+
+import httpx
+import uvicorn
+from fastapi import FastAPI, Request, Query
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse
+from sse_starlette import EventSourceResponse
+
+# Constants
+SERVER_VERSION = "0.2.0"
+SERVER_NAME = "biel-ai-mcp"
+DEFAULT_PORT = 7832
+DEFAULT_BASE_URL = "https://app.biel.ai"
+BIEL_API_PATH = "/api/v1/chats"
+MCP_PROTOCOL_VERSION = "2024-11-05"
+REQUEST_TIMEOUT = 30.0
+KEEPALIVE_INTERVAL = 30
+
+# Error codes
+JSON_PARSE_ERROR = -32700
+UNKNOWN_METHOD_ERROR = -1
+
+# Setup logging
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger("biel-mcp")
+
+# Tool definitions
+TOOLS = [
+ {
+ "name": "biel_ai",
+ "description": "Query Biel.ai's specialized AI about code, SDKs and documentation",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "Your question about code, SDK or documentation"
+ },
+ "base_url": {
+ "type": "string",
+ "description": "Base URL of your Biel.ai instance",
+ "default": DEFAULT_BASE_URL
+ },
+ "project_slug": {
+ "type": "string",
+ "description": "Project slug for your Biel.ai project"
+ },
+ "api_key": {
+ "type": "string",
+ "description": "API key for authentication (optional)",
+ "default": ""
+ },
+ "chat_uuid": {
+ "type": "string",
+ "description": "Chat UUID to continue conversation (optional)",
+ "default": ""
+ },
+ "domain": {
+ "type": "string",
+ "description": "Domain URL to pass to Biel.ai as context (optional)",
+ "default": "app.biel.ai"
+ }
+ },
+ "required": ["message"]
+ }
+ }
+]
+
+# FastAPI app setup
+app = FastAPI(title="Biel.ai MCP Server", version=SERVER_VERSION)
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+
+def create_error_response(message: str) -> Dict[str, str]:
+ """Create a standardized error response."""
+ return {"type": "text", "text": f"Error: {message}"}
+
+
+def create_success_response(text: str) -> Dict[str, str]:
+ """Create a standardized success response."""
+ return {"type": "text", "text": text}
+
+
+def validate_biel_request(arguments: Dict[str, Any]) -> Optional[str]:
+ """Validate Biel.ai request arguments. Returns error message if invalid, None if valid."""
+ if not arguments.get("message", "").strip():
+ return "Message cannot be empty"
+
+ if not arguments.get("project_slug", "").strip():
+ return "Project slug is required"
+
+ return None
+
+
+def format_biel_response(data: Dict[str, Any]) -> str:
+ """Format the response from Biel.ai API into a readable string."""
+ ai_message = data.get("ai_message", {})
+ ai_response = ai_message.get("message", "No response received")
+ chat_uuid = data.get("chat_uuid", "")
+ sources = ai_message.get("sources", [])
+
+ response_parts = [f"🤖 **Biel.ai responds:**\n\n{ai_response}"]
+
+ if sources:
+ response_parts.append("\n\n📚 **Sources consulted:**")
+ for source in sources:
+ response_parts.append(f"• [{source['title']}]({source['url']})")
+
+ if chat_uuid:
+ response_parts.append(f"\n💬 *Chat UUID: {chat_uuid}* (to continue conversation)")
+
+ return "\n".join(response_parts)
+
+
+async def query_biel_ai(arguments: Dict[str, Any], defaults: Dict[str, str] = None) -> Dict[str, str]:
+ """Query Biel.ai API with the provided arguments."""
+ # Apply defaults from connection if not provided in arguments
+ if defaults:
+ if not arguments.get("project_slug") and defaults.get("project_slug"):
+ arguments["project_slug"] = defaults["project_slug"]
+
+ if not arguments.get("api_key") and defaults.get("api_key"):
+ arguments["api_key"] = defaults["api_key"]
+
+ if not arguments.get("base_url") and defaults.get("base_url"):
+ arguments["base_url"] = defaults["base_url"]
+
+ if not arguments.get("domain") and defaults.get("domain"):
+ arguments["domain"] = defaults["domain"]
+
+ # Validate input
+ validation_error = validate_biel_request(arguments)
+ if validation_error:
+ return create_error_response(validation_error)
+
+ # Extract arguments
+ message = arguments["message"]
+ base_url = arguments.get("base_url", DEFAULT_BASE_URL)
+ project_slug = arguments["project_slug"]
+ api_key = arguments.get("api_key", "")
+ chat_uuid = arguments.get("chat_uuid", "")
+ domain = arguments.get("domain", "")
+
+ # Prepare request
+ payload = {
+ "message": message,
+ "project_slug": project_slug,
+ "url": domain if domain else base_url
+ }
+
+ if chat_uuid:
+ payload["chat_uuid"] = chat_uuid
+
+ headers = {"Content-Type": "application/json"}
+ if api_key:
+ headers["Authorization"] = f"Api-Key {api_key}"
+
+ full_url = f"{base_url.rstrip('/')}{BIEL_API_PATH}/"
+
+ logger.info(f"Querying Biel.ai: {message[:50]}... (project: {project_slug})")
+
+ try:
+ async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
+ response = await client.post(full_url, json=payload, headers=headers)
+
+ if response.status_code in (200, 201):
+ data = response.json()
+ formatted_response = format_biel_response(data)
+ return create_success_response(formatted_response)
+ else:
+ error_msg = f"HTTP {response.status_code}: {response.text}"
+ logger.error(f"Biel.ai API error: {error_msg}")
+ return create_error_response(f"Biel.ai API error: {error_msg}")
+
+ except httpx.TimeoutException:
+ logger.error("Timeout querying Biel.ai")
+ return create_error_response("⏱️ Timeout: Biel.ai took too long to respond")
+ except Exception as e:
+ logger.error(f"Unexpected error querying Biel.ai: {e}")
+ return create_error_response(f"Unexpected error: {str(e)}")
+
+
+def create_mcp_response(msg_id: Optional[str], result: Optional[Dict] = None,
+ error: Optional[Dict] = None) -> Dict[str, Any]:
+ """Create a standardized MCP JSON-RPC response."""
+ response = {
+ "jsonrpc": "2.0",
+ "id": msg_id
+ }
+
+ if error:
+ response["error"] = error
+ else:
+ response["result"] = result or {}
+
+ return response
+
+
+async def handle_mcp_request(data: Dict[str, Any], defaults: Dict[str, str] = None) -> Dict[str, Any]:
+ """Handle MCP protocol messages."""
+ try:
+ method = data.get("method")
+ msg_id = data.get("id")
+
+ logger.info(f"Handling MCP request: {method}")
+
+ if method == "initialize":
+ return create_mcp_response(msg_id, {
+ "protocolVersion": MCP_PROTOCOL_VERSION,
+ "capabilities": {"tools": {}},
+ "serverInfo": {
+ "name": SERVER_NAME,
+ "version": SERVER_VERSION
+ }
+ })
+
+ elif method == "tools/list":
+ return create_mcp_response(msg_id, {"tools": TOOLS})
+
+ elif method == "tools/call":
+ params = data.get("params", {})
+ tool_name = params.get("name")
+ arguments = params.get("arguments", {})
+
+ if tool_name == "biel_ai":
+ result = await query_biel_ai(arguments, defaults)
+ return create_mcp_response(msg_id, {"content": [result]})
+ else:
+ return create_mcp_response(
+ msg_id,
+ error={"code": UNKNOWN_METHOD_ERROR, "message": f"Unknown tool: {tool_name}"}
+ )
+
+ else:
+ return create_mcp_response(
+ msg_id,
+ error={"code": UNKNOWN_METHOD_ERROR, "message": f"Unknown method: {method}"}
+ )
+
+ except Exception as e:
+ logger.error(f"Error handling MCP message: {e}")
+ return create_mcp_response(
+ data.get("id") if isinstance(data, dict) else None,
+ error={"code": UNKNOWN_METHOD_ERROR, "message": str(e)}
+ )
+
+
+async def mcp_sse_generator(request: Request, message: Optional[str] = None, defaults: Dict[str, str] = None):
+ """Generate SSE events for MCP protocol."""
+ try:
+ if message:
+ try:
+ mcp_request = json.loads(message)
+ logger.info(f"Processing MCP request: {mcp_request.get('method', 'unknown')}")
+
+ response = await handle_mcp_request(mcp_request, defaults)
+ yield {
+ "event": "message",
+ "data": json.dumps(response)
+ }
+
+ except json.JSONDecodeError:
+ yield {
+ "event": "message",
+ "data": json.dumps(create_mcp_response(
+ None,
+ error={"code": JSON_PARSE_ERROR, "message": "Parse error"}
+ ))
+ }
+ else:
+ # Send initial connection event
+ yield {
+ "event": "message",
+ "data": json.dumps({
+ "jsonrpc": "2.0",
+ "method": "notifications/initialized",
+ "params": {}
+ })
+ }
+
+ # Keep connection alive
+ while True:
+ await asyncio.sleep(KEEPALIVE_INTERVAL)
+ yield {"event": "ping", "data": ""}
+
+ except asyncio.CancelledError:
+ logger.info("SSE connection cancelled")
+ except Exception as e:
+ logger.error(f"SSE error: {e}")
+
+
+# API Routes
+@app.get("/")
+async def health_check():
+ """Health check endpoint."""
+ return {
+ "status": "healthy",
+ "service": SERVER_NAME,
+ "version": SERVER_VERSION,
+ "usage": {
+ "endpoint": "/sse",
+ "query_params": {
+ "project_slug": "Your Biel.ai project slug",
+ "api_key": "Your API key (optional)",
+ "base_url": "Biel.ai instance URL (optional, defaults to https://app.biel.ai)",
+ "domain": "Domain URL to pass as context to Biel.ai (optional)"
+ },
+ "example": "/sse?project_slug=your-slug&api_key=your-key&domain=https://example.com"
+ }
+ }
+
+
+@app.get("/sse")
+async def sse_endpoint(
+ request: Request,
+ message: Optional[str] = Query(None),
+ project_slug: Optional[str] = Query(None),
+ api_key: Optional[str] = Query(None),
+ base_url: Optional[str] = Query(None),
+ domain: Optional[str] = Query(None)
+):
+ """MCP Server-Sent Events endpoint with query parameters for configuration."""
+ # Build defaults from query parameters
+ defaults = {}
+ if project_slug:
+ defaults["project_slug"] = project_slug
+ if api_key:
+ defaults["api_key"] = api_key
+ if base_url:
+ defaults["base_url"] = base_url
+ if domain:
+ defaults["domain"] = domain
+
+ return EventSourceResponse(mcp_sse_generator(request, message, defaults))
+
+
+@app.post("/sse")
+async def sse_post_endpoint(
+ request: Request,
+ project_slug: Optional[str] = Query(None),
+ api_key: Optional[str] = Query(None),
+ base_url: Optional[str] = Query(None),
+ domain: Optional[str] = Query(None)
+):
+ """Handle POST requests to SSE endpoint with query parameters for configuration."""
+ # Build defaults from query parameters
+ defaults = {}
+ if project_slug:
+ defaults["project_slug"] = project_slug
+ if api_key:
+ defaults["api_key"] = api_key
+ if base_url:
+ defaults["base_url"] = base_url
+ if domain:
+ defaults["domain"] = domain
+
+ try:
+ data = await request.json()
+ response = await handle_mcp_request(data, defaults)
+ return JSONResponse(response)
+ except Exception as e:
+ logger.error(f"Error handling POST to SSE: {e}")
+ return JSONResponse(
+ create_mcp_response(None, error={"code": UNKNOWN_METHOD_ERROR, "message": str(e)}),
+ status_code=500
+ )
+
+
+@app.options("/sse")
+async def sse_options():
+ """Handle OPTIONS requests for CORS preflight."""
+ return JSONResponse(
+ content={},
+ headers={
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
+ }
+ )
+
+
+if __name__ == "__main__":
+ logger.info(f"🚀 Starting {SERVER_NAME} server v{SERVER_VERSION} on port {DEFAULT_PORT}")
+ logger.info(f"🌐 Access via: http://localhost:{DEFAULT_PORT}/sse")
+
+ uvicorn.run(app, host="0.0.0.0", port=DEFAULT_PORT)
\ No newline at end of file
diff --git a/demo 2.png b/demo 2.png
new file mode 100644
index 0000000..db6471a
Binary files /dev/null and b/demo 2.png differ
diff --git a/docker-compose 2.yml b/docker-compose 2.yml
new file mode 100644
index 0000000..38d77ca
--- /dev/null
+++ b/docker-compose 2.yml
@@ -0,0 +1,16 @@
+version: '3.8'
+
+services:
+ biel-mcp-server:
+ build: .
+ ports:
+ - "7832:7832"
+ environment:
+ - PYTHONUNBUFFERED=1
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:7832/sse"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
\ No newline at end of file
diff --git a/example-mcp 2.json b/example-mcp 2.json
new file mode 100644
index 0000000..e3b272f
--- /dev/null
+++ b/example-mcp 2.json
@@ -0,0 +1,12 @@
+{
+ "mcpServers": {
+ "biel-ai": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "https://mcp.biel.ai/sse?project_slug=YOUR_PROJECT_SLUG&domain=https://your-docs-domain.com"
+ ],
+ "description": "Query your product's documentation, APIs, and knowledge base. Ask about API specs, guides, and troubleshooting."
+ }
+ }
+}
\ No newline at end of file
diff --git a/logo 2.jpg b/logo 2.jpg
new file mode 100644
index 0000000..8dfb31b
Binary files /dev/null and b/logo 2.jpg differ
diff --git a/logo-dark 2.jpg b/logo-dark 2.jpg
new file mode 100644
index 0000000..01bbc84
Binary files /dev/null and b/logo-dark 2.jpg differ
diff --git a/requirements 2.txt b/requirements 2.txt
new file mode 100644
index 0000000..07a845f
--- /dev/null
+++ b/requirements 2.txt
@@ -0,0 +1,4 @@
+mcp>=1.0.0
+httpx>=0.24.0
+uvicorn>=0.24.0
+fastapi>=0.104.0
\ No newline at end of file