Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Enable or disable running the MCP server
RUN_MCP_SERVER=true

# LLM provider (anthropic, openai, mistral, gemini, ollama)
# LLM provider (anthropic, openai)
LM_PROVIDER=anthropic

# Default model name for the LLM
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This repository bundles the OCL Model Context Protocol (MCP) server together wit

- Python 3.10+ (3.12 is used inside the Docker image) with access to the filesystem where the repo lives.
- An optional OCL API token when you need access to private or organization-owned repositories.
- Optional LLM provider credentials (for example, Anthropic) if you plan to use the UI’s chat experience.
- Optional LLM provider credentials for Anthropic or OpenAI if you plan to use the UI’s chat experience.

## Configuration

Expand All @@ -27,7 +27,7 @@ Important variables:
|---|---|
| `OCL_TOKEN` | OCL API token (40 characters) for authenticated concept/mapping requests. |
| `OCL_URL` | Override for the OCL base URL; `chat` defaults to production but allows `staging`, `local`, or any custom URL. |
| `LM_PROVIDER` | Sets a default provider in the UI (defaults to `anthropic`). |
| `LM_PROVIDER` | Sets a default provider in the UI. Supported values are `anthropic` and `openai` (defaults to `anthropic`). |
| `LM_MODEL` | Preferred model name for the chat session. |
| `LM_TOKEN` | API key for the chosen LLM provider (passed straight to the UI). |
| `UI_HOST` / `UI_PORT` | Host and port for the FastAPI app (default `0.0.0.0:8002`). |
Expand Down Expand Up @@ -82,4 +82,3 @@ Compose loads `.env`, exposes port `8002`, and uses the healthcheck in `docker-c

- See `mcp-server/README.md` for detailed MCP server usage, tooling, and available MCP endpoints.
- `mcp-server/docs/` contains auxiliary guides (Claude configuration, examples, etc.).
*** End Patch
10 changes: 7 additions & 3 deletions mcp-client/routes/frontend.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import os
import json
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Request, Body
from fastapi import APIRouter, Body, HTTPException, Request
from fastapi.responses import RedirectResponse, JSONResponse
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool, StructuredTool
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage

from mcp import types as mcp_types
from routes.providers import normalize_provider
from web_config import templates
from ocl_mcp.server import OCLMCPServer
from ocl_mcp.tools import (
Expand Down Expand Up @@ -235,13 +236,16 @@ async def chat_message(request: Request, payload: dict = Body(...)):

# Initialize LLM
config_payload = payload.get("config", {})
provider = config_payload.get("provider") or os.getenv("LM_PROVIDER", "anthropic")
try:
provider = normalize_provider(config_payload.get("provider") or os.getenv("LM_PROVIDER"))
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
model_name = config_payload.get("model") or os.getenv("LM_MODEL")
api_key = config_payload.get("api_key") or os.getenv("LM_TOKEN")

if provider == "openai":
llm = ChatOpenAI(model=model_name or "gpt-4o", api_key=api_key)
else:
elif provider == "anthropic":
llm = ChatAnthropic(model=model_name or "claude-3-5-sonnet-20241022", api_key=api_key)

# Bind tools
Expand Down
10 changes: 10 additions & 0 deletions mcp-client/routes/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
DEFAULT_PROVIDER = "anthropic"
SUPPORTED_LM_PROVIDERS = ("anthropic", "openai")


def normalize_provider(provider):
provider = (provider or DEFAULT_PROVIDER).strip().lower() or DEFAULT_PROVIDER
if provider not in SUPPORTED_LM_PROVIDERS:
supported = ", ".join(SUPPORTED_LM_PROVIDERS)
raise ValueError(f"Unsupported LLM provider '{provider}'. Supported providers: {supported}.")
return provider
3 changes: 0 additions & 3 deletions mcp-client/templates/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ <h2>Session configuration</h2>
<select id="provider" name="provider" required>
<option value="openai">OpenAI</option>
<option value="anthropic">Anthropic</option>
<option value="mistral">Mistral</option>
<option value="gemini">Google Gemini</option>
<option value="ollama">Ollama</option>
</select>
</div>
<div class="form-field">
Expand Down
42 changes: 42 additions & 0 deletions tests/test_provider_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import importlib.util
import unittest
from pathlib import Path


PROVIDER_MODULE_PATH = (
Path(__file__).resolve().parents[1] / "mcp-client" / "routes" / "providers.py"
)


def load_provider_module():
spec = importlib.util.spec_from_file_location("provider_config", PROVIDER_MODULE_PATH)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


class ProviderConfigTest(unittest.TestCase):
def test_normalize_provider_defaults_to_anthropic(self):
providers = load_provider_module()

self.assertEqual(providers.normalize_provider(None), "anthropic")
self.assertEqual(providers.normalize_provider(""), "anthropic")

def test_normalize_provider_accepts_supported_providers_case_insensitively(self):
providers = load_provider_module()

self.assertEqual(providers.normalize_provider(" OpenAI "), "openai")
self.assertEqual(providers.normalize_provider("ANTHROPIC"), "anthropic")

def test_normalize_provider_rejects_unsupported_providers(self):
providers = load_provider_module()

with self.assertRaises(ValueError) as context:
providers.normalize_provider("gemini")

self.assertIn("Unsupported LLM provider 'gemini'", str(context.exception))
self.assertIn("anthropic, openai", str(context.exception))


if __name__ == "__main__":
unittest.main()