forkflux-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forkflux_mcp-0.1.0/.gitignore +35 -0
- forkflux_mcp-0.1.0/PKG-INFO +57 -0
- forkflux_mcp-0.1.0/README.md +33 -0
- forkflux_mcp-0.1.0/forkflux_mcp/__init__.py +0 -0
- forkflux_mcp-0.1.0/forkflux_mcp/constants.py +24 -0
- forkflux_mcp-0.1.0/forkflux_mcp/main.py +391 -0
- forkflux_mcp-0.1.0/forkflux_mcp/schemas.py +10 -0
- forkflux_mcp-0.1.0/pyproject.toml +51 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
.idea
|
|
13
|
+
.mypy_cache
|
|
14
|
+
.ruff_cache
|
|
15
|
+
.pytest_cache
|
|
16
|
+
.env
|
|
17
|
+
local.yml
|
|
18
|
+
docker_data
|
|
19
|
+
|
|
20
|
+
AGENTS.md
|
|
21
|
+
.aiginore
|
|
22
|
+
.agents
|
|
23
|
+
.clinerules
|
|
24
|
+
.clineignore
|
|
25
|
+
.roorules
|
|
26
|
+
.roo
|
|
27
|
+
.rooignore
|
|
28
|
+
.zoorules
|
|
29
|
+
.zoo
|
|
30
|
+
.zooignore
|
|
31
|
+
.cursor
|
|
32
|
+
.cursorignore
|
|
33
|
+
.claude
|
|
34
|
+
.kilocodeignore
|
|
35
|
+
.kilo
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forkflux-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model Context Protocol (MCP) adapter for ForkFlux, enabling AI coding assistants to securely publish and claim jobs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/forkflux/forkflux
|
|
6
|
+
Project-URL: Repository, https://github.com/forkflux/forkflux
|
|
7
|
+
Project-URL: Issues, https://github.com/forkflux/forkflux/issues
|
|
8
|
+
Keywords: agentic-workflow,ai-agents,claude-code,coordination-bus,cursor,developer-tools,mcp,model-context-protocol
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: fastmcp==3.4.2
|
|
21
|
+
Requires-Dist: httpx==0.28.1
|
|
22
|
+
Requires-Dist: pydantic==2.13.4
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# ForkFlux MCP Server
|
|
26
|
+
|
|
27
|
+
> Model Context Protocol (MCP) server for ForkFlux, the coordination bus that lets isolated AI coding agents publish, claim, and close structured handoff jobs.
|
|
28
|
+
|
|
29
|
+
ForkFlux MCP connects MCP-compatible assistants such as Cursor, Claude Code, and Cline to a ForkFlux API instance. It exposes a small set of agent-facing tools for decentralized engineering workflows: create a job with full context, list available work for the current role, atomically claim a job, and update its final status.
|
|
30
|
+
|
|
31
|
+
Use this package when you want AI agents on separate machines or workspaces to exchange work without copy-pasting logs, sharing local files, or using human task trackers as an ad-hoc data bus.
|
|
32
|
+
|
|
33
|
+
## What it provides
|
|
34
|
+
|
|
35
|
+
- `forkflux_create_job` — publish a structured handoff job with constraints, context, artifacts, priority, and target role.
|
|
36
|
+
- `forkflux_list_jobs` — list jobs available in the shared ForkFlux job pool.
|
|
37
|
+
- `forkflux_claim_job` — atomically claim a published job and receive its full context payload.
|
|
38
|
+
- `forkflux_change_job_status` — close claimed work as `completed`, `failed`, or `cancelled`.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Python 3.12+
|
|
43
|
+
- A running ForkFlux API endpoint
|
|
44
|
+
- A ForkFlux API key for the agent using this MCP server
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Set these environment variables before starting the server:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export FORKFLUX_API_URL="http://localhost:8000/api/v1"
|
|
52
|
+
export FORKFLUX_API_KEY="your-agent-api-key"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
Apache-2.0. See the project repository for full license details.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ForkFlux MCP Server
|
|
2
|
+
|
|
3
|
+
> Model Context Protocol (MCP) server for ForkFlux, the coordination bus that lets isolated AI coding agents publish, claim, and close structured handoff jobs.
|
|
4
|
+
|
|
5
|
+
ForkFlux MCP connects MCP-compatible assistants such as Cursor, Claude Code, and Cline to a ForkFlux API instance. It exposes a small set of agent-facing tools for decentralized engineering workflows: create a job with full context, list available work for the current role, atomically claim a job, and update its final status.
|
|
6
|
+
|
|
7
|
+
Use this package when you want AI agents on separate machines or workspaces to exchange work without copy-pasting logs, sharing local files, or using human task trackers as an ad-hoc data bus.
|
|
8
|
+
|
|
9
|
+
## What it provides
|
|
10
|
+
|
|
11
|
+
- `forkflux_create_job` — publish a structured handoff job with constraints, context, artifacts, priority, and target role.
|
|
12
|
+
- `forkflux_list_jobs` — list jobs available in the shared ForkFlux job pool.
|
|
13
|
+
- `forkflux_claim_job` — atomically claim a published job and receive its full context payload.
|
|
14
|
+
- `forkflux_change_job_status` — close claimed work as `completed`, `failed`, or `cancelled`.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Python 3.12+
|
|
19
|
+
- A running ForkFlux API endpoint
|
|
20
|
+
- A ForkFlux API key for the agent using this MCP server
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
Set these environment variables before starting the server:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export FORKFLUX_API_URL="http://localhost:8000/api/v1"
|
|
28
|
+
export FORKFLUX_API_KEY="your-agent-api-key"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
Apache-2.0. See the project repository for full license details.
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import Enum, IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JobStatusEnum(str, Enum):
|
|
5
|
+
PUBLISHED = "published"
|
|
6
|
+
CLAIMED = "claimed"
|
|
7
|
+
IN_PROGRESS = "in_progress"
|
|
8
|
+
COMPLETED = "completed"
|
|
9
|
+
FAILED = "failed"
|
|
10
|
+
CANCELLED = "cancelled"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JobChangeStatusEnum(str, Enum):
|
|
14
|
+
IN_PROGRESS = "in_progress"
|
|
15
|
+
COMPLETED = "completed"
|
|
16
|
+
FAILED = "failed"
|
|
17
|
+
CANCELLED = "cancelled"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JobPriorityEnum(IntEnum):
|
|
21
|
+
LOW = 10
|
|
22
|
+
NORMAL = 20
|
|
23
|
+
HIGH = 30
|
|
24
|
+
URGENT = 40
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated, Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
from forkflux_mcp.constants import JobChangeStatusEnum, JobPriorityEnum, JobStatusEnum
|
|
11
|
+
from forkflux_mcp.schemas import JobArtifact
|
|
12
|
+
|
|
13
|
+
FORKFLUX_INSTRUCTIONS = """
|
|
14
|
+
You are connected to the ForkFlux Coordination Bus, an infrastructure layer for decentralized AI agents to securely hand off jobs across isolated machines.
|
|
15
|
+
|
|
16
|
+
You do not have a fixed role. You must dynamically act as either a Source or a Target based on what the user is asking you to do right now.
|
|
17
|
+
|
|
18
|
+
WHEN THE USER ASKS YOU TO HAND OFF WORK (Acting as Source):
|
|
19
|
+
- When a job requires execution by another agent (e.g., passing code to a QA agent), use `forkflux_create_job`.
|
|
20
|
+
- Provide explicit, strict `constraints` and embed all necessary context in the `context_payload` and all necessary artifacts in the `artifacts`.
|
|
21
|
+
|
|
22
|
+
WHEN THE USER ASKS YOU TO CHECK FOR OR RECEIVE NEW WORK (Acting as Target):
|
|
23
|
+
1. Use `forkflux_list_jobs` to find available jobs with status 'published'.
|
|
24
|
+
2. Display the available jobs to the user and proactively ask: "Shall I claim the first task in this list (<Job ID>), or would you like to specify another one?"
|
|
25
|
+
3. Wait for the user's response. Once they confirm the first task or provide a specific one, automatically extract the `job_id` and use `forkflux_claim_job`. This tool will return the FULL context payload immediately (Fat Claim).
|
|
26
|
+
4. EXTREMELY IMPORTANT: If the claim fails with a "409 Conflict" (Job already claimed), DO NOT complain to the user or stop. Silently fetch the list again and try claiming the next available job.
|
|
27
|
+
5. Once claimed, automatically analyze the returned `context_payload` and begin your work. Do not ask the user for permission to start unless specifically instructed.
|
|
28
|
+
6. Complete the work locally, then update the status to 'completed' or 'failed' (include a failure_reason if it failed) using `forkflux_change_job_status`.
|
|
29
|
+
""" # noqa: E501
|
|
30
|
+
|
|
31
|
+
mcp = FastMCP(
|
|
32
|
+
"ForkFlux",
|
|
33
|
+
instructions=FORKFLUX_INSTRUCTIONS,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
API_URL = os.environ.get("FORKFLUX_API_URL", "http://localhost:8000/api/v1")
|
|
37
|
+
API_KEY = os.environ.get("FORKFLUX_API_KEY")
|
|
38
|
+
|
|
39
|
+
if not API_KEY:
|
|
40
|
+
print("Warning: FORKFLUX_API_KEY is not set.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def _api_request(
|
|
44
|
+
method: str, endpoint: str, params: dict[str, Any] | None = None, json_data: dict[str, Any] | None = None
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
headers = {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"Authorization": f"Bearer {API_KEY}",
|
|
49
|
+
}
|
|
50
|
+
url = f"{API_URL}{endpoint}"
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
async with httpx.AsyncClient() as client:
|
|
54
|
+
response = await client.request(method, url, headers=headers, params=params, json=json_data)
|
|
55
|
+
if response.is_success:
|
|
56
|
+
if response.status_code == 204:
|
|
57
|
+
return {"success": True, "details": None}
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
return {"success": True, "details": response.json()}
|
|
61
|
+
except ValueError:
|
|
62
|
+
return {"success": True, "details": None}
|
|
63
|
+
|
|
64
|
+
if response.status_code in (400, 422):
|
|
65
|
+
try:
|
|
66
|
+
error_data = response.json()
|
|
67
|
+
except ValueError:
|
|
68
|
+
error_data = response.text
|
|
69
|
+
return {
|
|
70
|
+
"success": False,
|
|
71
|
+
"error": "Validation Error",
|
|
72
|
+
"status_code": response.status_code,
|
|
73
|
+
"details": error_data,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if response.status_code == 401:
|
|
77
|
+
raise RuntimeError("Wrong API key.")
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"success": False,
|
|
81
|
+
"error": "HTTP Error",
|
|
82
|
+
"status_code": response.status_code,
|
|
83
|
+
"details": response.text,
|
|
84
|
+
}
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return {"success": False, "error": "Network or Internal Error", "details": str(e)}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def get_dynamic_roles_enum() -> Enum:
|
|
90
|
+
list_available_roles = await _api_request("GET", "/agents/roles")
|
|
91
|
+
if list_available_roles["success"]:
|
|
92
|
+
available_roles = [x["role_key"] for x in list_available_roles["details"]]
|
|
93
|
+
else:
|
|
94
|
+
available_roles = []
|
|
95
|
+
return Enum("TargetRoleEnum", {role: role for role in available_roles})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
TargetRoleEnum = asyncio.run(get_dynamic_roles_enum())
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool("forkflux_create_job")
|
|
102
|
+
async def create_job(
|
|
103
|
+
summary: str,
|
|
104
|
+
context_payload: dict[str, Any],
|
|
105
|
+
target_role_key: TargetRoleEnum, # type: ignore[valid-type]
|
|
106
|
+
constraints: list[str],
|
|
107
|
+
artifacts: list[JobArtifact],
|
|
108
|
+
priority: JobPriorityEnum,
|
|
109
|
+
parent_job_id: int | None = None,
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
Publishes a new handoff job to the ForkFlux coordination bus for delegation.
|
|
113
|
+
|
|
114
|
+
CRITICAL: The Target Agent operates in complete isolation. They cannot see your
|
|
115
|
+
local workspace, files, or chat history. You MUST pack all necessary context
|
|
116
|
+
into the parameters below.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
summary: A concise, human-readable title of the job.
|
|
120
|
+
context_payload: A highly detailed, structured JSON dictionary. Do NOT pass a simple flat string.
|
|
121
|
+
Include relevant code snippets, error logs, state descriptions, and steps to reproduce.
|
|
122
|
+
target_role_key: The required specialization for this job.
|
|
123
|
+
constraints: A list of strict constraints or execution boundaries the Target Agent must follow.
|
|
124
|
+
artifacts: A list of external resources (like S3 URIs, Git commits, or database dumps) attached to this job.
|
|
125
|
+
priority: The urgency of the job.
|
|
126
|
+
parent_job_id: (Optional) The ID of the job that spawned this job, used for tracing the handoff chain.
|
|
127
|
+
"""
|
|
128
|
+
serialized_artifacts = [artifact.model_dump() for artifact in artifacts] if artifacts else []
|
|
129
|
+
|
|
130
|
+
return await _api_request(
|
|
131
|
+
"POST",
|
|
132
|
+
"/jobs",
|
|
133
|
+
json_data={
|
|
134
|
+
"summary": summary,
|
|
135
|
+
"context_payload": context_payload,
|
|
136
|
+
"target_role_key": target_role_key.value, # type: ignore[attr-defined]
|
|
137
|
+
"constraints": constraints,
|
|
138
|
+
"artifacts": serialized_artifacts,
|
|
139
|
+
"priority": priority,
|
|
140
|
+
"parent_job_id": parent_job_id,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@mcp.tool("forkflux_list_jobs")
|
|
146
|
+
async def list_jobs(
|
|
147
|
+
limit: Annotated[int, Field(default=50, ge=1, le=200)] = 50,
|
|
148
|
+
status: JobStatusEnum | None = JobStatusEnum.PUBLISHED,
|
|
149
|
+
target_role_key: TargetRoleEnum | None = None, # type: ignore[valid-type]
|
|
150
|
+
my_role_only: bool = True,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Fetches a list of jobs from the ForkFlux Coordination Bus.
|
|
154
|
+
Target Agents should use this tool to poll the Coordination Bus for available jobs to claim.
|
|
155
|
+
|
|
156
|
+
CRITICAL: Parse the response and present it as a clean, human-readable summary table or list.
|
|
157
|
+
DO NOT output the raw JSON to the user, as context payloads are too large.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
limit: The maximum number of jobs to return (min 1, max 200). Default is 50.
|
|
161
|
+
status: Filter by job lifecycle status. Defaults to 'published' (jobs ready to be claimed).
|
|
162
|
+
target_role_key: Filter jobs explicitly intended for a specific agent role.
|
|
163
|
+
my_role_only: If True (default), filters the pool to return only jobs matching the current agent's role.
|
|
164
|
+
"""
|
|
165
|
+
return await _api_request(
|
|
166
|
+
"GET",
|
|
167
|
+
"/jobs?order=priority_desc&order=created_at_asc",
|
|
168
|
+
params={
|
|
169
|
+
"limit": limit,
|
|
170
|
+
"status": status.value if status else None,
|
|
171
|
+
"target_role_key": target_role_key.value if target_role_key else None, # type: ignore[attr-defined]
|
|
172
|
+
"my_role_only": my_role_only,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@mcp.tool("forkflux_claim_job")
|
|
178
|
+
async def claim_job(job_id: Annotated[int, Field(description="The unique ID of the job to claim.")]):
|
|
179
|
+
"""
|
|
180
|
+
Atomically claims a published job from the ForkFlux coordination bus and returns its FULL context.
|
|
181
|
+
|
|
182
|
+
Target Agents MUST call this tool immediately after deciding to take a job.
|
|
183
|
+
Claiming transitions the job status to 'in_progress' and locks it for you.
|
|
184
|
+
|
|
185
|
+
If the claim fails (e.g., returns an HTTP 409 Conflict error), it means
|
|
186
|
+
another agent has already claimed this job. Do not proceed with the work;
|
|
187
|
+
instead, fetch the list of jobs again to find a new one.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
job_id: The ID of the job you want to lock and claim for yourself.
|
|
191
|
+
"""
|
|
192
|
+
return await _api_request("POST", f"/jobs/{job_id}/claim")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@mcp.tool("forkflux_change_job_status")
|
|
196
|
+
async def change_job_status(
|
|
197
|
+
job_id: Annotated[int, Field(description="The unique ID of the job.")],
|
|
198
|
+
status: JobChangeStatusEnum,
|
|
199
|
+
failure_reason: Annotated[
|
|
200
|
+
str | None,
|
|
201
|
+
Field(
|
|
202
|
+
description="A detailed explanation of why the job failed. REQUIRED if status is 'failed' otherwise ignore."
|
|
203
|
+
),
|
|
204
|
+
] = None,
|
|
205
|
+
):
|
|
206
|
+
"""
|
|
207
|
+
Updates the execution lifecycle status of a job you have claimed.
|
|
208
|
+
|
|
209
|
+
Claiming a job automatically transitions it to 'in_progress'.
|
|
210
|
+
Target Agents should use this tool for manual transitions only:
|
|
211
|
+
1. 'completed': Set this ONLY when you have successfully finished the job and met ALL constraints.
|
|
212
|
+
2. 'failed': Set this if an unrecoverable error occurs. You MUST populate `failure_reason`.
|
|
213
|
+
3. 'cancelled': Set this if the user explicitly asks you to abort.
|
|
214
|
+
"""
|
|
215
|
+
return await _api_request(
|
|
216
|
+
"POST", f"/jobs/{job_id}/status", json_data={"status": status.value, "failure_reason": failure_reason}
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@mcp.prompt("board")
|
|
221
|
+
def board_prompt() -> str:
|
|
222
|
+
"""
|
|
223
|
+
Fetch available published jobs strictly for the agent's current role from the ForkFlux shared pool.
|
|
224
|
+
Use this prompt when the user wants to see the dashboard/board of ready-to-claim tasks.
|
|
225
|
+
"""
|
|
226
|
+
return """
|
|
227
|
+
You are an AI Agent operating within the ForkFlux Coordination Bus protocol.
|
|
228
|
+
Your current goal is to fetch and display the board of available tasks waiting for your specific role.
|
|
229
|
+
|
|
230
|
+
Follow these instruction steps carefully:
|
|
231
|
+
|
|
232
|
+
1. Call the `forkflux_list_jobs` MCP tool with the exact following arguments:
|
|
233
|
+
- `status`: "published"
|
|
234
|
+
- `target_role_key`: null
|
|
235
|
+
- `my_role_only`: true
|
|
236
|
+
|
|
237
|
+
2. CRITICAL: Do not modify, omit, or guess these parameters. They are strictly required by the protocol to isolate work meant for your role.
|
|
238
|
+
|
|
239
|
+
3. Error Handling: If the tool call fails, returns a connection error, or an API alert, output the exact error message to the user and STOP. Do not hallucinate, imagine, or mock any jobs.
|
|
240
|
+
|
|
241
|
+
4. Empty State: If the returned list from the tool is empty, kindly inform the user that there are currently no published tasks available for your role in the ForkFlux shared pool.
|
|
242
|
+
|
|
243
|
+
5. Output Formatting (STRICT RULE):
|
|
244
|
+
- NEVER dump raw JSON payloads directly to the user.
|
|
245
|
+
- Parse the JSON response from the tool and present the jobs as a clean, highly readable Markdown table.
|
|
246
|
+
- The table MUST contain the following columns:
|
|
247
|
+
* **Job ID**: Rendered as inline code (e.g., `job_123`) for easy copying.
|
|
248
|
+
* **Priority**: The execution priority value (e.g., 10, 20, 30).
|
|
249
|
+
* **Source / Creator**: Who created the task (if the field is available in the payload).
|
|
250
|
+
* **Summary**: A brief, truncated snippet of the task's `constraints`.
|
|
251
|
+
* **Created**: The exact date and time when the task was published.
|
|
252
|
+
|
|
253
|
+
6. Next Step / Tool Chaining: Conclude your response by proactively asking the user:
|
|
254
|
+
"Shall I claim the first task in this list (<Job ID>), or would you like to specify another one?"
|
|
255
|
+
|
|
256
|
+
7. Execution Trigger: Wait for the user's response.
|
|
257
|
+
- If the user confirms to take the first task (e.g., says "yes", "go ahead", etc.), automatically extract its `job_id` and call the `forkflux_claim_job` tool.
|
|
258
|
+
- If the user specifies a different task from the list, extract that specific `job_id` and call the `forkflux_claim_job` tool.
|
|
259
|
+
""" # noqa: E501
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@mcp.prompt("claim")
|
|
263
|
+
def claim_prompt() -> str:
|
|
264
|
+
"""
|
|
265
|
+
Atomically claim a specific job from the ForkFlux coordination bus,
|
|
266
|
+
retrieve its full context payload (Fat Claim), and prepare for execution.
|
|
267
|
+
Use this prompt when the user passes a specific job ID to claim and start working on.
|
|
268
|
+
"""
|
|
269
|
+
return """
|
|
270
|
+
You are an AI Agent operating within the ForkFlux Coordination Bus protocol.
|
|
271
|
+
Your current goal is to atomically claim a task, lock it to prevent race conditions, and unpack its full context.
|
|
272
|
+
|
|
273
|
+
Follow these execution steps carefully:
|
|
274
|
+
|
|
275
|
+
1. PRE-CHECK: Verify that a valid `job_id` is available in the user's request.
|
|
276
|
+
- If the `job_id` is missing, stop and explicitly ask the user to provide it, or suggest they run the `board` prompt first to pick a task.
|
|
277
|
+
|
|
278
|
+
2. TOOL CALL: Call the `forkflux_claim_job` MCP tool using the provided `job_id`.
|
|
279
|
+
|
|
280
|
+
3. RACE CONDITION HANDLING (409 Conflict):
|
|
281
|
+
- CRITICAL: If the tool returns a `409 Conflict` error, it means another agent on a different machine has already snatched this task.
|
|
282
|
+
- DO NOT hallucinate a successful state.
|
|
283
|
+
- Inform the user politely but clearly that the job is already claimed by someone else, and suggest running the `ff_board` prompt to select a new one.
|
|
284
|
+
|
|
285
|
+
4. ERROR HANDLING: If the tool call fails for any other connection or API reason, output the exact error message and STOP.
|
|
286
|
+
|
|
287
|
+
5. FAT CLAIM ANALYSIS:
|
|
288
|
+
- Upon a successful response, the tool will return the FULL context payload of the job (including constraints, payload artifacts, and internal guidelines).
|
|
289
|
+
- Read, parse, and analyze this payload thoroughly to build your local execution context.
|
|
290
|
+
|
|
291
|
+
6. TOOL CHAINING & NEXT STEP:
|
|
292
|
+
- You are now the official owner of this task. Briefly summarize the core objective of the task based on the unpacked payload.
|
|
293
|
+
- Proceed to ask the user for confirmation to begin execution.
|
|
294
|
+
|
|
295
|
+
7. OUTPUT FORMATTING (STRICT RULE):
|
|
296
|
+
- NEVER dump raw JSON response payloads directly to the user.
|
|
297
|
+
- Provide a brief, energetic confirmation in Markdown format using the exact structure below:
|
|
298
|
+
|
|
299
|
+
🔒 **Job Claimed**: [Insert the `job_id` as inline code] — [Insert a 1-sentence human-readable summary of the objective].
|
|
300
|
+
🚦 **Status**: Confirmed as `IN_PROGRESS` (API payload value: `in_progress`).
|
|
301
|
+
📦 **Context Received**: Confirmed that the task payload and constraints have been successfully unpacked.
|
|
302
|
+
🚀 **Next Action**: Ask the user: *"Shall I start executing this task now?"*
|
|
303
|
+
""" # noqa: E501
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@mcp.prompt("close")
|
|
307
|
+
def close_prompt() -> str:
|
|
308
|
+
"""
|
|
309
|
+
Close a specific ForkFlux job by updating its lifecycle status to a terminal state
|
|
310
|
+
(completed, failed, or cancelled). Use this prompt when finishing execution.
|
|
311
|
+
"""
|
|
312
|
+
return """
|
|
313
|
+
You are an AI Agent operating within the ForkFlux Coordination Bus protocol.
|
|
314
|
+
Your current goal is to transition a specific job into its final terminal state, broadcasting this update to the decentralized bus.
|
|
315
|
+
|
|
316
|
+
CRITICAL INFRASTRUCTURE RULE: NEVER attempt to use bash, curl, or terminal commands to execute this state transition. You MUST use the provided `forkflux_change_job_status` MCP tool.
|
|
317
|
+
|
|
318
|
+
Follow these execution steps carefully:
|
|
319
|
+
|
|
320
|
+
1. PRE-CHECK: Ensure you have a valid `job_id` from your active context and the target termination status.
|
|
321
|
+
|
|
322
|
+
2. STATUS VALIDATION: Validate that the target status is strictly one of the allowed TERMINAL lifecycle states:
|
|
323
|
+
- `completed`
|
|
324
|
+
- `failed`
|
|
325
|
+
- `cancelled`
|
|
326
|
+
* CRITICAL: Do NOT use this command to transition a job back to `in_progress` or `published`.
|
|
327
|
+
|
|
328
|
+
3. STATE GATEKEEPING RULES (Verify before calling the tool):
|
|
329
|
+
- If status is `completed`: Only call this if you have verified that all code is written, tests pass successfully, and every single constraint from the job context payload is fully met.
|
|
330
|
+
- If status is `failed`: Call this if an unrecoverable error occurs, tests persistently fail, environment blockers arise, or constraints cannot be resolved.
|
|
331
|
+
- If status is `cancelled`: Call this if the user explicitly instructs you to abort the execution midway.
|
|
332
|
+
|
|
333
|
+
4. MANDATORY ERROR LOGGING: If you are setting the status to `failed`, you MUST populate the `failure_reason` argument with a highly detailed summary. Include stack trace excerpts, logs, or specific unmet constraints so human developers can trace and debug the handoff block.
|
|
334
|
+
|
|
335
|
+
5. TOOL CALL: Execute the `forkflux_change_job_status` MCP tool with the exact validated parameters.
|
|
336
|
+
|
|
337
|
+
6. TRANSACTION FAILURE HANDLING: If the tool call fails or returns a state-machine transition error from the Coordination Bus, output the exact error message and STOP. Do not hallucinate or assume a successful update.
|
|
338
|
+
|
|
339
|
+
7. OUTPUT FORMATTING (STRICT RULE):
|
|
340
|
+
- NEVER dump raw JSON response payloads from the tool directly to the user.
|
|
341
|
+
- Provide a clear, high-visibility status update block in Markdown using the exact structure below:
|
|
342
|
+
|
|
343
|
+
🔄 **Job Closed**: [Insert the `job_id` as inline code]
|
|
344
|
+
🚦 **Final State**: `[completed, failed, or cancelled]`
|
|
345
|
+
📝 **Summary / Error Details**:
|
|
346
|
+
- (If completed): [Provide a brief 1-2 sentence human-readable summary of what was implemented to meet the constraints]
|
|
347
|
+
- (If failed): [Print the explicit `failure_reason` that was provided to the tool]
|
|
348
|
+
""" # noqa: E501
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@mcp.prompt("push")
|
|
352
|
+
def push_prompt() -> str:
|
|
353
|
+
"""
|
|
354
|
+
Create and publish a new handoff job into the ForkFlux Coordination Bus.
|
|
355
|
+
Use this prompt when the current agent finishes its task and wants to route the work to another agent/role.
|
|
356
|
+
"""
|
|
357
|
+
return """
|
|
358
|
+
You are an AI Agent operating as a Source Agent within the ForkFlux Coordination Bus protocol.
|
|
359
|
+
Your goal is to package the current execution context, artifacts, and strict constraints, and publish them as a new handoff job.
|
|
360
|
+
|
|
361
|
+
CRITICAL INFRASTRUCTURE RULE: NEVER attempt to use bash, curl, or terminal commands to issue this API call. You MUST exclusively use the provided ForkFlux MCP tools.
|
|
362
|
+
|
|
363
|
+
Follow these execution steps carefully:
|
|
364
|
+
|
|
365
|
+
1. TOOL CHAINING (Role Discovery):
|
|
366
|
+
- Analyze available target role keys, match the correct one based on the user's workflow intent, and proceed. Never guess or hallucinate a role key.
|
|
367
|
+
|
|
368
|
+
2. PARAMETER PREPARATION (Validate before calling `forkflux_create_job`):
|
|
369
|
+
- `target_role_key`: (String) The exact valid key found via tool chaining.
|
|
370
|
+
- `constraints`: (list[str] / Array of Strings) Explicit constraint entries. Pass multiple constraints as an array of strings; each array item should clearly state what the next agent must achieve to consider this job complete.
|
|
371
|
+
- `context_payload`: (JSON/Dictionary) A highly detailed, structured JSON object. Pack the context of the work you just finished, specific code paths, environment nuances, and any implicit bugs/problems you tried to bypass. CRITICAL: Do NOT pass a simple flat string or raw text block here. It must be a valid structured JSON map.
|
|
372
|
+
- `priority`: (Integer) Must be exactly one of the allowed protocol enums: 10, 20, 30, or 40.
|
|
373
|
+
- `artifacts`: (Array of Objects) List of generated files, diffs, or logs. Only include real, verified files from the current directory. Do not hallucinate hashes, checksums, or non-existent URIs.
|
|
374
|
+
|
|
375
|
+
3. TOOL CALL: Execute the `forkflux_create_job` MCP tool with the prepared payload.
|
|
376
|
+
|
|
377
|
+
4. ERROR HANDLING: If the tool returns a validation or protocol bus error, output the exact error message and STOP. Do not retry with fake or modified parameters.
|
|
378
|
+
|
|
379
|
+
5. OUTPUT FORMATTING (STRICT RULE):
|
|
380
|
+
- Provide a clear, high-visibility status update block in Markdown using the exact structure below.
|
|
381
|
+
- Do NOT dump the raw JSON `context_payload` into the final success chat. Keep it concise.
|
|
382
|
+
|
|
383
|
+
🚀 **Job Published**: [Insert the newly created `job_id` as inline code]
|
|
384
|
+
🎯 **Target Role**: [Insert the `target_role_key` as inline code]
|
|
385
|
+
✅ **Constraints**: [Provide a brief 1-2 sentence human-readable summary of the constraints passed to the next agent]
|
|
386
|
+
📦 **Context Packed**: [Briefly summarize what metadata and technical logs you embedded into the `context_payload`]
|
|
387
|
+
""" # noqa: E501
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
mcp.run()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "forkflux-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Model Context Protocol (MCP) adapter for ForkFlux, enabling AI coding assistants to securely publish and claim jobs."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
keywords = [
|
|
8
|
+
"mcp",
|
|
9
|
+
"model-context-protocol",
|
|
10
|
+
"ai-agents",
|
|
11
|
+
"cursor",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"coordination-bus",
|
|
14
|
+
"agentic-workflow",
|
|
15
|
+
"developer-tools"
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: Apache Software License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Topic :: System :: Distributed Computing",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
dependencies = [
|
|
31
|
+
"fastmcp==3.4.2",
|
|
32
|
+
"httpx==0.28.1",
|
|
33
|
+
"pydantic==2.13.4",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/forkflux/forkflux"
|
|
38
|
+
Repository = "https://github.com/forkflux/forkflux"
|
|
39
|
+
Issues = "https://github.com/forkflux/forkflux/issues"
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = [
|
|
47
|
+
"forkflux_mcp/**",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["forkflux_mcp"]
|