@locusai/mcp 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Locus AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@locusai/mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "dependencies": {
7
+ "@modelcontextprotocol/sdk": "1.25.2",
8
+ "zod": "^3.23.8",
9
+ "@locusai/shared": "workspace:*"
10
+ }
11
+ }
package/src/api.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { API_BASE } from "./config.js";
2
+ import type { ToolResult } from "./types.js";
3
+
4
+ export function success(data: unknown): ToolResult {
5
+ return {
6
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
7
+ };
8
+ }
9
+
10
+ export function error(message: string): ToolResult {
11
+ return {
12
+ content: [{ type: "text", text: `Error: ${message}` }],
13
+ isError: true,
14
+ };
15
+ }
16
+
17
+ export async function apiGet<T>(path: string): Promise<T> {
18
+ const res = await fetch(`${API_BASE}${path}`);
19
+ return res.json();
20
+ }
21
+
22
+ export async function apiPost<T>(
23
+ path: string,
24
+ body: Record<string, unknown>
25
+ ): Promise<{ data: T; status: number; ok: boolean }> {
26
+ const res = await fetch(`${API_BASE}${path}`, {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/json" },
29
+ body: JSON.stringify(body),
30
+ });
31
+ const data = await res.json();
32
+ return { data, status: res.status, ok: res.ok };
33
+ }
34
+
35
+ export async function apiPatch<T>(
36
+ path: string,
37
+ body: Record<string, unknown>
38
+ ): Promise<T> {
39
+ const res = await fetch(`${API_BASE}${path}`, {
40
+ method: "PATCH",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify(body),
43
+ });
44
+ return res.json();
45
+ }
package/src/config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ const { values } = parseArgs({
4
+ args: Bun.argv,
5
+ options: {
6
+ project: { type: "string" },
7
+ },
8
+ strict: true,
9
+ allowPositionals: true,
10
+ });
11
+
12
+ if (!values.project) {
13
+ console.error("Usage: bun run mcp -- --project <workspaceDir>");
14
+ process.exit(1);
15
+ }
16
+
17
+ export const projectDir = values.project;
18
+ export const API_BASE = "http://localhost:3080/api";
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bun
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+
5
+ // Import config to trigger CLI arg parsing
6
+ import "./config.js";
7
+
8
+ // Import tool registration functions
9
+ import { registerArtifactTools } from "./tools/artifacts.js";
10
+ import { registerCiTools } from "./tools/ci.js";
11
+ import { registerDocsTools } from "./tools/docs.js";
12
+ import { registerKanbanTools } from "./tools/kanban.js";
13
+
14
+ // Create server instance
15
+ const server = new McpServer({
16
+ name: "locus",
17
+ version: "0.1.0",
18
+ });
19
+
20
+ // Register all tools
21
+ registerDocsTools(server);
22
+ registerKanbanTools(server);
23
+ registerArtifactTools(server);
24
+ registerCiTools(server);
25
+
26
+ // Start the server
27
+ async function main() {
28
+ try {
29
+ const transport = new StdioServerTransport();
30
+ await server.connect(transport);
31
+ console.error("Locus MCP server running on stdio");
32
+ } catch (error) {
33
+ console.error("Failed to start MCP server:", error);
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ main();
@@ -0,0 +1,40 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, error, success } from "../api.js";
4
+
5
+ export function registerArtifactTools(server: McpServer): void {
6
+ server.registerTool(
7
+ "artifacts.list",
8
+ {
9
+ title: "List Artifacts",
10
+ description:
11
+ "List all artifacts for a task (including implementation drafts)",
12
+ inputSchema: { taskId: z.number() },
13
+ },
14
+ async ({ taskId }) => {
15
+ try {
16
+ const data = await apiGet(`/artifacts?taskId=${taskId}`);
17
+ return success(data);
18
+ } catch (e) {
19
+ return error(String(e));
20
+ }
21
+ }
22
+ );
23
+
24
+ server.registerTool(
25
+ "artifacts.get",
26
+ {
27
+ title: "Get Artifact",
28
+ description: "Get the content of a specific artifact by ID",
29
+ inputSchema: { artifactId: z.number() },
30
+ },
31
+ async ({ artifactId }) => {
32
+ try {
33
+ const data = await apiGet(`/artifacts/${artifactId}`);
34
+ return success(data);
35
+ } catch (e) {
36
+ return error(String(e));
37
+ }
38
+ }
39
+ );
40
+ }
@@ -0,0 +1,25 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiPost, error, success } from "../api.js";
4
+
5
+ export function registerCiTools(server: McpServer): void {
6
+ server.registerTool(
7
+ "ci.run",
8
+ {
9
+ title: "Run CI",
10
+ description: "Run a CI preset (e.g., 'quick' or 'full') for a task",
11
+ inputSchema: {
12
+ taskId: z.number(),
13
+ preset: z.string(),
14
+ },
15
+ },
16
+ async ({ taskId, preset }) => {
17
+ try {
18
+ const { data } = await apiPost("/ci/run", { taskId, preset });
19
+ return success(data);
20
+ } catch (e) {
21
+ return error(String(e));
22
+ }
23
+ }
24
+ );
25
+ }
@@ -0,0 +1,61 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, apiPost, error, success } from "../api.js";
4
+
5
+ export function registerDocsTools(server: McpServer): void {
6
+ server.registerTool(
7
+ "docs.tree",
8
+ {
9
+ title: "Documentation Tree",
10
+ description: "Get the documentation tree structure",
11
+ inputSchema: {},
12
+ },
13
+ async () => {
14
+ try {
15
+ const data = await apiGet("/docs/tree");
16
+ return success(data);
17
+ } catch (e) {
18
+ return error(String(e));
19
+ }
20
+ }
21
+ );
22
+
23
+ server.registerTool(
24
+ "docs.read",
25
+ {
26
+ title: "Read Document",
27
+ description: "Read a document from the documentation",
28
+ inputSchema: { path: z.string() },
29
+ },
30
+ async ({ path }) => {
31
+ try {
32
+ const data = await apiGet(
33
+ `/docs/read?path=${encodeURIComponent(path)}`
34
+ );
35
+ return success(data);
36
+ } catch (e) {
37
+ return error(String(e));
38
+ }
39
+ }
40
+ );
41
+
42
+ server.registerTool(
43
+ "docs.write",
44
+ {
45
+ title: "Write Document",
46
+ description: "Write content to a document",
47
+ inputSchema: {
48
+ path: z.string(),
49
+ content: z.string(),
50
+ },
51
+ },
52
+ async ({ path, content }) => {
53
+ try {
54
+ const { data } = await apiPost("/docs/write", { path, content });
55
+ return success(data);
56
+ } catch (e) {
57
+ return error(String(e));
58
+ }
59
+ }
60
+ );
61
+ }
@@ -0,0 +1,298 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, apiPatch, apiPost, error, success } from "../api.js";
4
+ import { projectDir } from "../config.js";
5
+ import { ROLE_PROMPTS, type Sprint, type Task } from "../types.js";
6
+
7
+ export function registerKanbanTools(server: McpServer): void {
8
+ // View active sprint tasks
9
+ server.registerTool(
10
+ "kanban.sprint",
11
+ {
12
+ title: "View Active Sprint",
13
+ description:
14
+ "View all tasks in the active sprint with their status. Use this to see sprint progress. To claim a task to work on, use kanban.next instead.",
15
+ inputSchema: {},
16
+ },
17
+ async () => {
18
+ try {
19
+ const sprints = await apiGet<Sprint[]>("/sprints");
20
+ const activeSprint = sprints.find((s) => s.status === "ACTIVE");
21
+
22
+ if (!activeSprint) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text" as const,
27
+ text: JSON.stringify(
28
+ {
29
+ error: "NO_ACTIVE_SPRINT",
30
+ message:
31
+ "No active sprint found. Please start a sprint from the Backlog before requesting tasks.",
32
+ },
33
+ null,
34
+ 2
35
+ ),
36
+ },
37
+ ],
38
+ isError: true,
39
+ };
40
+ }
41
+
42
+ // Get tasks for this sprint
43
+ const tasks = await apiGet<Task[]>(
44
+ `/tasks?sprintId=${activeSprint.id}`
45
+ );
46
+
47
+ return success({
48
+ sprint: { id: activeSprint.id, name: activeSprint.name },
49
+ tasks: tasks.map((t) => ({
50
+ id: t.id,
51
+ title: t.title,
52
+ status: t.status,
53
+ priority: t.priority,
54
+ assigneeRole: t.assigneeRole,
55
+ })),
56
+ hint: "Use kanban.next to claim and start working on the next available task.",
57
+ });
58
+ } catch (e) {
59
+ return error(String(e));
60
+ }
61
+ }
62
+ );
63
+
64
+ // Priority queue - get next task to work on from active sprint
65
+ server.registerTool(
66
+ "kanban.next",
67
+ {
68
+ title: "Get & Claim Next Task",
69
+ description:
70
+ "START HERE: Get and claim the next highest priority task from the active sprint. This is the PRIMARY way to get a task to work on. The task is automatically assigned to you. Returns full task details including acceptance criteria.",
71
+ inputSchema: {
72
+ workerId: z.string().optional(),
73
+ },
74
+ },
75
+ async ({ workerId }) => {
76
+ try {
77
+ // First, check if there's an active sprint
78
+ const sprints = await apiGet<Sprint[]>("/sprints");
79
+ const activeSprint = sprints.find((s) => s.status === "ACTIVE");
80
+
81
+ if (!activeSprint) {
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text" as const,
86
+ text: JSON.stringify(
87
+ {
88
+ error: "NO_ACTIVE_SPRINT",
89
+ message:
90
+ "No active sprint found. Please start a sprint from the Backlog before requesting tasks.",
91
+ },
92
+ null,
93
+ 2
94
+ ),
95
+ },
96
+ ],
97
+ isError: true,
98
+ };
99
+ }
100
+
101
+ // Dispatch task from the active sprint
102
+ const {
103
+ data: task,
104
+ status,
105
+ ok,
106
+ } = await apiPost<Task>("/tasks/dispatch", {
107
+ workerId,
108
+ sprintId: String(activeSprint.id),
109
+ });
110
+
111
+ if (status === 404) {
112
+ return success({
113
+ message: `No tasks available in active sprint "${activeSprint.name}"`,
114
+ sprintId: activeSprint.id,
115
+ sprintName: activeSprint.name,
116
+ });
117
+ }
118
+
119
+ if (!ok) {
120
+ throw new Error("Failed to dispatch task");
121
+ }
122
+
123
+ // Inject system prompts
124
+ const systemInstructions =
125
+ task.assigneeRole && ROLE_PROMPTS[task.assigneeRole];
126
+
127
+ return success({
128
+ message: "Assigned task",
129
+ sprint: { id: activeSprint.id, name: activeSprint.name },
130
+ task: { ...task, systemInstructions },
131
+ });
132
+ } catch (e) {
133
+ return error(String(e));
134
+ }
135
+ }
136
+ );
137
+
138
+ // Get specific task details
139
+ server.registerTool(
140
+ "kanban.get",
141
+ {
142
+ title: "Get Task Details",
143
+ description:
144
+ "Get full details of a task you already know the ID for. Use kanban.next to claim a new task instead of this.",
145
+ inputSchema: { taskId: z.number() },
146
+ },
147
+ async ({ taskId }) => {
148
+ try {
149
+ const data = await apiGet<Task>(`/tasks/${taskId}`);
150
+ const systemInstructions =
151
+ data.assigneeRole && ROLE_PROMPTS[data.assigneeRole];
152
+ return success({ ...data, systemInstructions });
153
+ } catch (e) {
154
+ return error(String(e));
155
+ }
156
+ }
157
+ );
158
+
159
+ // Move task status
160
+ server.registerTool(
161
+ "kanban.move",
162
+ {
163
+ title: "Move Task Status",
164
+ description:
165
+ "Update task status. When work is complete, move to VERIFICATION (NOT DONE). Valid statuses: IN_PROGRESS, VERIFICATION. The DONE status is reserved for manual approval only.",
166
+ inputSchema: {
167
+ taskId: z.number(),
168
+ status: z.string(),
169
+ },
170
+ },
171
+ async ({ taskId, status }) => {
172
+ try {
173
+ const data = await apiPatch(`/tasks/${taskId}`, { status });
174
+ return success(data);
175
+ } catch (e) {
176
+ return error(String(e));
177
+ }
178
+ }
179
+ );
180
+
181
+ // Commit changes
182
+ server.registerTool(
183
+ "kanban.commit",
184
+ {
185
+ title: "Commit Task Changes",
186
+ description:
187
+ "Create a git commit with the task ID in the message. Call this BEFORE moving to VERIFICATION to save your work.",
188
+ inputSchema: {
189
+ taskId: z.number(),
190
+ additionalMessage: z.string().optional(),
191
+ },
192
+ },
193
+ async ({ taskId, additionalMessage }) => {
194
+ try {
195
+ const task = await apiGet<Task>(`/tasks/${taskId}`);
196
+
197
+ if (!task.title) {
198
+ return error("Task not found");
199
+ }
200
+
201
+ // Build commit message
202
+ let commitMessage = `Task #${taskId}: ${task.title}`;
203
+ if (additionalMessage) {
204
+ commitMessage += `\n\n${additionalMessage}`;
205
+ }
206
+
207
+ // Execute git commands
208
+ const addProc = Bun.spawn(["git", "add", "-A"], {
209
+ cwd: projectDir,
210
+ stdout: "pipe",
211
+ stderr: "pipe",
212
+ });
213
+ await addProc.exited;
214
+
215
+ const commitProc = Bun.spawn(["git", "commit", "-m", commitMessage], {
216
+ cwd: projectDir,
217
+ stdout: "pipe",
218
+ stderr: "pipe",
219
+ });
220
+ const exitCode = await commitProc.exited;
221
+ const stdout = await new Response(commitProc.stdout).text();
222
+ const stderr = await new Response(commitProc.stderr).text();
223
+
224
+ if (exitCode !== 0) {
225
+ return success({
226
+ success: false,
227
+ error: stderr || "Git commit failed",
228
+ hint: "Make sure there are staged changes to commit",
229
+ });
230
+ }
231
+
232
+ return success({
233
+ success: true,
234
+ message: `Committed changes for Task #${taskId}`,
235
+ commitMessage,
236
+ output: stdout,
237
+ });
238
+ } catch (e) {
239
+ return error(String(e));
240
+ }
241
+ }
242
+ );
243
+
244
+ // Check acceptance criteria
245
+ server.registerTool(
246
+ "kanban.check",
247
+ {
248
+ title: "Check Acceptance Item",
249
+ description:
250
+ "Mark acceptance checklist items as completed. Pass the full updated checklist array with completed items marked.",
251
+ inputSchema: {
252
+ taskId: z.number(),
253
+ acceptanceChecklist: z.array(
254
+ z.object({
255
+ id: z.string(),
256
+ text: z.string(),
257
+ done: z.boolean(),
258
+ })
259
+ ),
260
+ },
261
+ },
262
+ async ({ taskId, acceptanceChecklist }) => {
263
+ try {
264
+ const data = await apiPatch(`/tasks/${taskId}`, {
265
+ acceptanceChecklist,
266
+ });
267
+ return success(data);
268
+ } catch (e) {
269
+ return error(String(e));
270
+ }
271
+ }
272
+ );
273
+
274
+ // Add comment
275
+ server.registerTool(
276
+ "kanban.comment",
277
+ {
278
+ title: "Add Comment",
279
+ description: "Add a comment to a task",
280
+ inputSchema: {
281
+ taskId: z.number(),
282
+ author: z.string(),
283
+ text: z.string(),
284
+ },
285
+ },
286
+ async ({ taskId, author, text }) => {
287
+ try {
288
+ const { data } = await apiPost(`/tasks/${taskId}/comment`, {
289
+ author,
290
+ text,
291
+ });
292
+ return success(data);
293
+ } catch (e) {
294
+ return error(String(e));
295
+ }
296
+ }
297
+ );
298
+ }
package/src/types.ts ADDED
@@ -0,0 +1,68 @@
1
+ export interface ToolResult {
2
+ [key: string]: unknown;
3
+ content: Array<{ type: "text"; text: string }>;
4
+ isError?: boolean;
5
+ }
6
+
7
+ export interface Task {
8
+ id: number;
9
+ title: string;
10
+ description?: string;
11
+ status: string;
12
+ priority: string;
13
+ assigneeRole?: string;
14
+ acceptanceChecklist?: Array<{
15
+ id: string;
16
+ text: string;
17
+ done: boolean;
18
+ }>;
19
+ }
20
+
21
+ export interface Sprint {
22
+ id: number;
23
+ name: string;
24
+ status: string;
25
+ }
26
+
27
+ export const ROLE_PROMPTS: Record<string, string> = {
28
+ FRONTEND: `## Frontend Implementation Guidelines
29
+
30
+ ### Design & Aesthetics
31
+ - **Visual Excellence**: Create stunning, premium interfaces. Use vibrant colors, glassmorphism, and smooth animations. Avoid generic or flat designs.
32
+ - **Modern Typography**: Use curated fonts (e.g., Inter, Roboto, Outfit). Avoid browser defaults.
33
+ - **Dynamic Interactions**: Add hover effects, micro-animations, and fluid transitions to make the UI feel alive.
34
+ - **Responsiveness**: Ensure flawless rendering across all device sizes.
35
+
36
+ ### Technical Standards
37
+ - **Component-Driven**: Build small, reusable components. Use props for customization.
38
+ - **State Management**: Keep state local where possible; use global state only when necessary.
39
+ - **Clean Code**: Use semantic HTML, proper naming, and avoid inline styles (use CSS classes/variables).
40
+ - **Performance**: Optimize images and minimize re-renders.
41
+
42
+ ### Workflow Rules (CRITICAL)
43
+ 1. **Branching**: A git branch is automatically created when task moves to IN_PROGRESS.
44
+ 2. **Working**: Implement the task, check all acceptance criteria in the task.
45
+ 3. **Committing**: Use \`kanban.commit\` when work is ready to save your changes.
46
+ 4. **Completion**: Move task to **VERIFICATION** using \`kanban.move(taskId, "VERIFICATION")\`.
47
+ 5. **NEVER move to DONE**: The system will reject direct DONE transitions. Only the manager can approve to DONE.
48
+ 6. **If Rejected**: Check task comments for feedback, fix issues, commit again, and move back to VERIFICATION.`,
49
+
50
+ BACKEND: `## Backend Implementation Guidelines
51
+
52
+ ### Architecture & Quality
53
+ - **Modularity**: Keep concerns separated (routes, controllers, services, db).
54
+ - **Type Safety**: Use strict TypeScript types. Avoid \`any\`.
55
+ - **Error Handling**: Gracefully handle errors and return standard HTTP status codes.
56
+
57
+ ### Security & Performance
58
+ - **Input Validation**: Validate all incoming data (zod/joi).
59
+ - **Efficiency**: Optimize database queries and avoid N+1 problems.
60
+
61
+ ### Workflow Rules (CRITICAL)
62
+ 1. **Branching**: A git branch is automatically created when task moves to IN_PROGRESS.
63
+ 2. **Working**: Implement the task, check all acceptance criteria in the task.
64
+ 3. **Committing**: Use \`kanban.commit\` when work is ready to save your changes.
65
+ 4. **Completion**: Move task to **VERIFICATION** using \`kanban.move(taskId, "VERIFICATION")\`.
66
+ 5. **NEVER move to DONE**: The system will reject direct DONE transitions. Only the manager can approve to DONE.
67
+ 6. **If Rejected**: Check task comments for feedback, fix issues, commit again, and move back to VERIFICATION.`,
68
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }