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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,42 @@ Phase 4: ECOSYSTEM (Batches 11+) → Adjacent tools, community, competitive c
| SaaS Product Evaluation | [saas-evaluation.md](templates/saas-evaluation.md) |
| Programming Framework | [programming-framework.md](templates/programming-framework.md) |

## MCP Server

BIF includes a stdio MCP server that exposes the framework as tools for MCP-compatible clients.

### Required environment

| Variable | Required | Purpose |
|----------|----------|---------|
| `BIF_SESSIONS_DIR` | No | Overrides the default temp-directory session store. Use this when you want sessions persisted in a known workspace path. |

If `BIF_SESSIONS_DIR` is not set, sessions are written under the operating system temp directory in `bif-sessions/`.

### Startup command

```bash
python mcp_server.py
```

Example MCP client configuration:

```json
{
"mcpServers": {
"bif": {
"command": "python",
"args": ["/absolute/path/to/bif/mcp_server.py"],
"env": {
"BIF_SESSIONS_DIR": "/absolute/path/to/bif/.sessions"
}
}
}
}
```

The server supports `initialize`, `tools/list`, `tools/call`, and `ping` over newline-delimited JSON-RPC on stdio.

## Proven Results

| Project | Batches | Files | Time | Coverage |
Expand Down
66 changes: 66 additions & 0 deletions tests/test_mcp_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Subprocess tests for the BIF MCP stdio server."""

from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).parent.parent
SERVER = REPO_ROOT / "mcp_server.py"


def _request(process: subprocess.Popen, payload: dict) -> dict:
assert process.stdin is not None
assert process.stdout is not None
process.stdin.write(json.dumps(payload) + "\n")
process.stdin.flush()
line = process.stdout.readline()
assert line, "MCP server did not emit a response"
return json.loads(line)


def test_mcp_server_starts_and_accepts_tool_invocation(tmp_path) -> None:
env = {**os.environ, "BIF_SESSIONS_DIR": str(tmp_path / "sessions")}
process = subprocess.Popen(
[sys.executable, str(SERVER)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=env,
)
try:
initialize = _request(
process,
{"jsonrpc": "2.0", "id": "init-1", "method": "initialize", "params": {}},
)
assert initialize["result"]["serverInfo"]["name"] == "bif"

tools = _request(
process,
{"jsonrpc": "2.0", "id": "tools-1", "method": "tools/list", "params": {}},
)
tool_names = {tool["name"] for tool in tools["result"]["tools"]}
assert "bif_start_session" in tool_names

call = _request(
process,
{
"jsonrpc": "2.0",
"id": "call-1",
"method": "tools/call",
"params": {
"name": "bif_start_session",
"arguments": {"domain": "MCP Test Domain"},
},
},
)
content = json.loads(call["result"]["content"][0]["text"])
assert content["domain"] == "MCP Test Domain"
assert (tmp_path / "sessions" / f"{content['session_id']}.json").exists()
finally:
process.terminate()
process.wait(timeout=5)