Skip to content
Merged
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
34 changes: 32 additions & 2 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,35 @@ headers = { Authorization = "Bearer …" }
A `[mcp.servers]` entry named `exa` or `context7` replaces the auto-configured
definition.

OAuth for HTTP servers is intentionally not wired up (it needs an interactive
browser flow); pass bearer tokens via `headers` instead.
## OAuth 2.1

HTTP servers that advertise OAuth (like GlitchTip) authenticate interactively:

```toml
[mcp.servers.glitchtip]
transport = "http"
url = "https://your-glitchtip.example.com/mcp"
auth = "oauth"
```

- On startup lecode connects with **cached credentials only** — an expired
access token is refreshed silently from its refresh token; a missing or
rejected refresh shows `authentication required`. Startup never blocks on
a browser.
- `/mcp auth glitchtip` runs the interactive login: it opens your browser
and prints the authorization URL in the feed (paste it into a different
browser if you prefer); the redirect lands back on a loopback port lecode
serves (`http://127.0.0.1:<port>/callback`).
- `/mcp logout glitchtip` drops the session and the persisted credentials.
- Credentials (access + refresh tokens, client registration) are stored per
endpoint under `~/.config/lecode/mcp-auth/` (0600 files, 0700 directory,
plaintext JSON). `LECODE_CONFIG_DIR` moves them.

The protocol itself — resource/server metadata discovery, dynamic client
registration, PKCE, token exchange and refresh - is handled by the SDK's
OAuth client; lecode supplies storage, the browser step, and the loopback
callback. `auth = "oauth"` conflicts with a static `Authorization` header
(that header path is the alternative for servers without OAuth).

## Permissions

Expand Down Expand Up @@ -68,6 +95,9 @@ Rule targets for MCP tools are the canonical `mcp:<server>:<tool>` name.

```
/mcp per-server state: connected (n tools) / failed / disabled
/ authentication required
/mcp tools <name> list one server's tools
/mcp reconnect <name> drop and re-establish a server session
/mcp auth <name> interactive OAuth login (opens the browser)
/mcp logout <name> drop a server session and its stored credentials
```
4 changes: 4 additions & 0 deletions src/lecode/agent/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ def __init__(self, tools: list[Tool] | None = None) -> None:
def register(self, tool: Tool) -> None:
self._tools[tool.name] = tool

def unregister(self, name: str) -> None:
"""Drop one tool by exact name (no-op when unknown)."""
self._tools.pop(name, None)

def get(self, name: str) -> Tool | None:
return self._tools.get(name)

Expand Down
4 changes: 4 additions & 0 deletions src/lecode/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ class McpServerConfig(BaseModel):
# http
url: str | None = None
headers: dict[str, str] = Field(default_factory=dict)
#: ``"oauth"`` enables the SDK's OAuth 2.1 flow (discovery, dynamic client
#: registration, PKCE). ``None`` keeps static ``headers`` (bearer token)
#: authentication. Only meaningful with ``transport = "http"``.
auth: Literal["oauth"] | None = None
# common
timeout_s: float = 30.0
enabled: bool = True
Expand Down
Loading
Loading