MCP (Model Context Protocol) server for HLV. Provides programmatic access to HLV project data and operations.
Adopted projects are supported: point --root at the repository root and the server reads HLV-owned resources from .hlv/. Use index resources/tools for compact symbol lookups instead of reading .hlv/index/signatures.yaml wholesale.
# stdio transport from inside an HLV project
hlv mcp
# stdio transport from outside the project root
hlv mcp --root /path/to/project
# SSE/HTTP transport (for web clients)
hlv mcp --transport sse --port 3000
# Workspace (multi-project) - several projects through one server
hlv mcp --workspace ~/.hlv/workspace.yaml
hlv mcp --workspace ~/.hlv/workspace.yaml --transport sse --port 3000.mcp.json in the project root (single-project mode):
{
"mcpServers": {
"hlv": {
"command": "hlv",
"args": ["mcp"],
"cwd": "/path/to/project"
}
}
}Workspace mode (multi-project):
{
"mcpServers": {
"hlv-workspace": {
"command": "hlv",
"args": ["mcp", "--workspace", "/home/dev/.hlv/workspace.yaml"]
}
}
}| Resource | URI | Description |
|---|---|---|
| Project | hlv://project |
Project configuration (project.yaml) |
| Milestones | hlv://milestones |
Current milestone, stages, history |
| Gates | hlv://gates |
Gate policy (gates-policy.yaml) |
| Constraints | hlv://constraints |
List of constraint files |
| Map | hlv://map |
LLM file navigator (map.yaml) |
| Workflow | hlv://workflow |
Current phase + recommended actions |
| Tasks | hlv://tasks |
All tasks across all stages |
| Artifacts | hlv://artifacts |
Global artifacts (metadata) |
| Contracts | hlv://contracts |
List of contracts for the current milestone |
| Plan | hlv://plan |
Plan structure (plan.md) |
| Traceability | hlv://traceability |
Requirement <-> contract <-> code links |
| Glossary | hlv://glossary |
Domain terms |
| Resource | URI Template | Description |
|---|---|---|
| Stage | hlv://stage/{n} |
Plan for a specific stage |
| Contract | hlv://contracts/{id} |
Full contract content |
| Tasks (stage) | hlv://tasks/{n} |
Tasks for a specific stage |
| Artifact | hlv://artifacts/{name} |
Global artifact content |
| Milestone artifacts | hlv://artifacts/milestone/{mid} |
Milestone artifacts (metadata) |
| Milestone artifact | hlv://artifacts/milestone/{mid}/{name} |
Milestone artifact content |
| Index symbol | hlv://index/symbol/{selector} |
Signature index entries matching a symbol name or index ID |
| Index file | hlv://index/file/{path} |
Signature index entries for a repository-relative file |
hlv://project
{
"contents": [{
"uri": "hlv://project",
"mimeType": "application/json",
"text": "{\"name\":\"payments\",\"version\":\"0.1.0\",\"methodology\":\"hlv\",\"paths\":{\"human\":\"human\",\"output\":\"output\"}}"
}]
}hlv://milestones
{
"contents": [{
"uri": "hlv://milestones",
"mimeType": "application/json",
"text": "{\"current\":{\"id\":\"001-order-create\",\"name\":\"Order Create\",\"status\":\"implementing\",\"stages\":[{\"id\":1,\"status\":\"validated\"},{\"id\":2,\"status\":\"implementing\"}],\"labels\":[],\"meta\":{}},\"history\":[]}"
}]
}hlv://tasks (all tasks)
{
"contents": [{
"uri": "hlv://tasks",
"mimeType": "application/json",
"text": "[{\"stage_id\":1,\"id\":\"S1-001\",\"status\":\"done\"},{\"stage_id\":2,\"id\":\"S2-001\",\"status\":\"in_progress\",\"labels\":[\"api\"],\"meta\":{\"owner\":\"team-a\"}}]"
}]
}hlv://stage/{n} (parameterized)
{
"contents": [{
"uri": "hlv://stage/1",
"mimeType": "application/json",
"text": "{\"id\":1,\"name\":\"Core API\",\"budget\":\"4h\",\"contracts\":[\"C-001\",\"C-002\"],\"tasks\":[{\"id\":\"S1-001\",\"name\":\"Create order endpoint\",\"contracts\":[\"C-001\"],\"depends_on\":[],\"output\":[\"src/order.rs\"]}],\"remediation\":[]}"
}]
}hlv://glossary
{
"contents": [{
"uri": "hlv://glossary",
"mimeType": "application/json",
"text": "{\"terms\":[{\"term\":\"Order\",\"definition\":\"A customer request to purchase items\",\"context\":\"domain\"}]}"
}]
}| Tool | Parameters | Description |
|---|---|---|
hlv_check |
- | Validate the project and return diagnostics |
hlv_workflow |
- | Current phase + recommended actions |
hlv_commit_msg |
stage_complete?, type? |
Generate a commit message |
| Tool | Parameters | Description |
|---|---|---|
hlv_milestone_new |
name |
Create a new milestone |
hlv_milestone_done |
- | Finish the current milestone |
hlv_milestone_abort |
- | Abort the current milestone |
hlv_milestone_label |
action (add/remove), label |
Manage labels |
hlv_milestone_meta |
action (set/delete), key, value? |
Manage metadata |
| Tool | Parameters | Description |
|---|---|---|
hlv_gate_enable |
id |
Enable a gate |
hlv_gate_disable |
id |
Disable a gate |
hlv_gate_run |
id? |
Run gate(s) |
| Tool | Parameters | Description |
|---|---|---|
hlv_constraint_add |
name, owner?, intent?, applies_to |
Add a constraint |
hlv_constraint_remove |
name |
Remove a constraint |
hlv_constraint_add_rule |
constraint, rule_id, severity, statement |
Add a rule |
hlv_constraint_remove_rule |
constraint, rule_id |
Remove a rule |
| Tool | Parameters | Description |
|---|---|---|
hlv_stage_reopen |
stage_id |
Reopen a stage (implemented→implementing, validated→validating) |
hlv_stage_label |
stage_id, action, label |
Add/remove a stage label |
hlv_stage_meta |
stage_id, action, key, value? |
Stage metadata |
| Tool | Parameters | Description |
|---|---|---|
hlv_task_list |
stage?, status?, label? |
List tasks with filters |
hlv_task_add |
stage_id, task_id, name |
Add a task (auto-reopens stage if needed) |
hlv_task_start |
task_id |
Start a task |
hlv_task_done |
task_id |
Complete a task |
hlv_task_block |
task_id, reason |
Block a task |
hlv_task_unblock |
task_id |
Unblock a task |
hlv_task_sync |
force? |
Sync tasks from stage files |
hlv_task_label |
task_id, action, label |
Add/remove a task label |
hlv_task_meta |
task_id, action, key, value? |
Task metadata |
| Tool | Parameters | Description |
|---|---|---|
hlv_artifacts |
scope?, name? |
List/show artifacts |
hlv_glossary |
- | Domain glossary |
| Tool | Parameters | Description |
|---|---|---|
hlv_index_show |
symbol |
Query compact signature index entries by symbol name or index ID |
hlv_index_list |
file |
List compact signature index entries for a repository-relative file |
hlv_task_list - request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "hlv_task_list",
"arguments": { "status": "in_progress" }
}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{
"type": "text",
"text": "[{\"stage_id\":2,\"id\":\"S2-001\",\"status\":\"in_progress\"}]"
}]
}
}hlv_check - request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "hlv_check", "arguments": {} }
}Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{
"type": "text",
"text": "{\"exit_code\":1,\"diagnostics\":[{\"code\":\"PRJ-010\",\"severity\":\"error\",\"message\":\"project.yaml not found\"}]}"
}]
}
}hlv_task_start - request:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "hlv_task_start",
"arguments": { "task_id": "S2-001" }
}
}Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{
"type": "text",
"text": "Task 'S2-001' started"
}]
}
}SSE mode only. Change subscriptions (
resources/subscribe) are available only when launched with--transport sse. In stdio mode, the file watcher is not started and theresources.subscribecapability is not advertised.
| File | Resources updated on change |
|---|---|
milestones.yaml |
hlv://milestones, hlv://tasks, hlv://workflow |
project.yaml |
hlv://project |
gates-policy.yaml |
hlv://gates |
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/subscribe",
"params": { "uri": "hlv://milestones" }
}When the file changes, the client receives a notification:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": "hlv://milestones" }
}JSON-RPC 2.0 over stdin/stdout. Used for Claude Code and CLI integrations.
hlv mcpHTTP server using the MCP Streamable HTTP transport. For web clients and dashboards.
hlv mcp --transport sse --port 3000- Endpoint:
POST /mcp— JSON-RPC requests; responses aretext/event-stream - Endpoint:
GET /mcp— reconnect to an existing SSE session (requiresMcp-Session-Id) - Endpoint:
DELETE /mcp— close session - CORS: all origins allowed (for browser clients)
- Graceful shutdown:
SIGINT/SIGTERM
Key transport details:
- Accept header is required. Every request must include
Accept: application/json, text/event-stream. The server returns406 Not Acceptablewithout it. - All responses are SSE. POST responses arrive as
text/event-stream, not plain JSON. Each JSON-RPC message is adata:line followed by a blank line. - POST initialize is the session entry point. The first POST with
initializecreates a session and returns a long-lived SSE stream. TheMcp-Session-Idheader in the response identifies the session. This stream stays open and carries server-pushed notifications (e.g.notifications/resources/updated). - Session lifetime = stream lifetime. If the SSE stream from
initializeis closed, the session is destroyed and subsequent requests return404 Session not found. UseGET /mcpwithMcp-Session-Idto reconnect before the session expires.
POST /mcp— sendinitialize; keep the response stream open (it is the notification channel); readMcp-Session-Idfrom the response header;POST /mcp— sendnotifications/initialized(includeMcp-Session-Id);POST /mcp— sendresources/subscribefor each URI;- Notifications arrive on the SSE stream from step 1;
DELETE /mcp— close session when done.
All POST responses (including tool calls and resource reads) arrive as SSE:
data: {"jsonrpc":"2.0","id":1,"result":{...}}
You cannot use response.json(). Parse SSE frames to extract the data: payload, then parse that as JSON.
JavaScript (SSE):
// Step 1: initialize — this response is a long-lived SSE stream
const response = await fetch('http://localhost:3000/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-03-26',
capabilities: {},
clientInfo: { name: 'my-client', version: '0.1.0' },
},
}),
});
// Read session ID from response header
const sessionId = response.headers.get('Mcp-Session-Id');
// Parse SSE stream for initialize response + future notifications
const reader = response.body.getReader();
const decoder = new TextDecoder();
// ... read and parse SSE frames from the stream
// Step 2: complete handshake (subsequent requests use sessionId)
await fetch('http://localhost:3000/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream',
'Mcp-Session-Id': sessionId,
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'notifications/initialized',
}),
});Python (stdio):
import subprocess, json
proc = subprocess.Popen(
['hlv', 'mcp'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
cwd='/path/to/project',
)
request = json.dumps({
"jsonrpc": "2.0", "id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "py-client", "version": "0.1"},
},
}) + "\n"
proc.stdin.write(request.encode())
proc.stdin.flush()Rust (rmcp client):
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
let config = StreamableHttpClientTransportConfig::with_uri("http://localhost:3000/mcp");
let transport = config.start_client().await?;
let client = ().serve(transport).await?;Workspace mode lets one MCP server serve multiple HLV projects.
hlv workspace init # create ~/.hlv/workspace.yaml
hlv workspace add # add current directory (ID = dir name)
hlv workspace add my-api # add current directory with explicit ID
hlv workspace add my-api --root /path/to/proj # add specific path
hlv workspace remove my-api # remove project from workspace
hlv workspace list # show all projectsUse --config <path> to use a non-default workspace file:
hlv workspace --config ~/custom-ws.yaml add# ~/.hlv/workspace.yaml
projects:
- id: backend
root: /home/dev/projects/backend
- id: mobile
root: /home/dev/projects/mobile-app
- id: infra
root: /home/dev/projects/infraEach project must contain project.yaml or .hlv/project.yaml at the specified root.
In workspace mode, all resources are available via hlv://projects/{id}/...:
| Resource | URI (single) | URI (workspace) |
|---|---|---|
| Project list | - | hlv://projects |
| Project | hlv://project |
hlv://projects/{id} |
| Milestones | hlv://milestones |
hlv://projects/{id}/milestones |
| Tasks | hlv://tasks |
hlv://projects/{id}/tasks |
| Stage | hlv://stage/{n} |
hlv://projects/{id}/stage/{n} |
| Index symbol | hlv://index/symbol/{selector} |
hlv://projects/{id}/index/symbol/{selector} |
| Index file | hlv://index/file/{path} |
hlv://projects/{id}/index/file/{path} |
| ... | hlv://{resource} |
hlv://projects/{id}/{resource} |
[
{
"id": "backend",
"root": "/home/dev/projects/backend",
"name": "payments-api",
"current_milestone": "001-order-create",
"milestone_status": "Implementing",
"stages_total": 3,
"stages_done": 1
},
{
"id": "mobile",
"root": "/home/dev/projects/mobile-app",
"name": "mobile-app",
"current_milestone": "002-auth",
"milestone_status": "Pending",
"stages_total": 2,
"stages_done": 0
}
]All tools accept an additional project_id parameter:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "hlv_task_list",
"arguments": {
"project_id": "backend",
"status": "in_progress"
}
}
}In single-project mode project_id is ignored and can be omitted.
In workspace mode project_id is required; omitting it is an error.
- Kanban across all projects:
hlv://projects-> iterate overhlv://projects/{id}/tasks - Overview dashboard: status of each project, aggregate progress
- AI assistant: switches between projects based on context
- Custom CLI/TUI: aggregated view across all projects
When using SSE transport, a file watcher is started for each project in the workspace.
Notifications work the same way: when milestones.yaml changes in project backend,
the server sends resources/updated for hlv://projects/backend/milestones
(and related scoped URIs such as hlv://projects/backend/tasks, hlv://projects/backend/workflow).