feedback-layer-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/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Feedback Layer MCP
2
+
3
+ Feedback Layer MCP exposes selected project feedback requests to coding agents.
4
+ It runs as a local stdio MCP server and talks to your Feedback Layer API over
5
+ HTTPS with a personal access token.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx -y feedback-layer-mcp@latest init
11
+ ```
12
+
13
+ The init command stores credentials in:
14
+
15
+ ```txt
16
+ ~/.config/feedback-layer/auth.json
17
+ ```
18
+
19
+ It also writes local MCP client configuration for Claude-style `.mcp.json` and
20
+ Codex `~/.codex/config.toml`.
21
+
22
+ ## Standard MCP Config
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "feedback-layer": {
28
+ "command": "npx",
29
+ "args": ["-y", "feedback-layer-mcp@latest"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Codex
36
+
37
+ ```bash
38
+ codex mcp add feedback-layer npx "feedback-layer-mcp@latest"
39
+ npx -y feedback-layer-mcp@latest init
40
+ ```
41
+
42
+ Or edit `~/.codex/config.toml`:
43
+
44
+ ```toml
45
+ [mcp_servers.feedback-layer]
46
+ command = "npx"
47
+ args = ["-y", "feedback-layer-mcp@latest"]
48
+ ```
49
+
50
+ ## Claude Code
51
+
52
+ ```bash
53
+ claude mcp add feedback-layer npx feedback-layer-mcp@latest
54
+ npx -y feedback-layer-mcp@latest init
55
+ ```
56
+
57
+ ## Cursor, Windsurf, VS Code and Other Clients
58
+
59
+ Use the standard MCP config above. Feedback Layer uses stdio transport, so the
60
+ client starts the package locally with `npx`.
61
+
62
+ ## Non-Interactive Setup
63
+
64
+ ```bash
65
+ npx -y feedback-layer-mcp@latest init \
66
+ --url https://feedback-layer.venture-ia.com \
67
+ --token flp_live_xxx \
68
+ --project findy
69
+ ```
70
+
71
+ Environment-only configuration also works:
72
+
73
+ ```bash
74
+ FL_API_BASE_URL=https://feedback-layer.venture-ia.com \
75
+ FL_PROJECT_SLUG=findy \
76
+ FL_TOKEN=flp_live_xxx \
77
+ npx -y feedback-layer-mcp@latest
78
+ ```
79
+
80
+ ## Commands
81
+
82
+ ```bash
83
+ feedback-layer-mcp init
84
+ feedback-layer-mcp doctor
85
+ feedback-layer-mcp status
86
+ ```
87
+
88
+ The short alias `fl-mcp` is also available.
89
+
90
+ ## Tools
91
+
92
+ - `list_tasks` - list ready tasks for the configured project
93
+ - `get_task` - read one task with implementation prompt and acceptance criteria
94
+ - `claim_task` - claim a task for the current local session
95
+ - `complete_task` - mark a claimed task complete with a PR URL
96
+ - `release_task` - release a claimed task back to the ready queue
97
+
98
+ ## Security
99
+
100
+ Do not commit tokens or generated client config. Keep `.mcp.json` local when it
101
+ contains environment secrets. Prefer `init`, which stores the token in a
102
+ user-local file with `0600` permissions.
@@ -0,0 +1,10 @@
1
+ type InitFlags = {
2
+ apiBaseUrl?: string;
3
+ token?: string;
4
+ projectSlug?: string;
5
+ repoUrl?: string;
6
+ help?: boolean;
7
+ };
8
+ export declare function parseInitFlags(args: string[]): InitFlags;
9
+ export declare function runInit(args?: string[]): Promise<void>;
10
+ export {};
@@ -0,0 +1,108 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ import { feedbackLayerApi } from "../client/api.js";
5
+ import { DEFAULT_API_BASE_URL, readAuthConfig, writeAuthConfig, } from "../client/auth.js";
6
+ import { writeClaudeConfig } from "../config/claude-code.js";
7
+ import { writeCodexConfig } from "../config/codex.js";
8
+ function readFlagValue(args, index, flag) {
9
+ const value = args[index + 1];
10
+ if (!value || value.startsWith("--")) {
11
+ throw new Error(`Missing value for ${flag}`);
12
+ }
13
+ return value;
14
+ }
15
+ export function parseInitFlags(args) {
16
+ const flags = {};
17
+ for (let i = 0; i < args.length; i += 1) {
18
+ const arg = args[i];
19
+ if (arg === "--help" || arg === "-h") {
20
+ flags.help = true;
21
+ }
22
+ else if (arg === "--url" || arg === "--api-base-url") {
23
+ flags.apiBaseUrl = readFlagValue(args, i, arg);
24
+ i += 1;
25
+ }
26
+ else if (arg === "--token") {
27
+ flags.token = readFlagValue(args, i, arg);
28
+ i += 1;
29
+ }
30
+ else if (arg === "--project" || arg === "--project-slug") {
31
+ flags.projectSlug = readFlagValue(args, i, arg);
32
+ i += 1;
33
+ }
34
+ else if (arg === "--repo-url") {
35
+ flags.repoUrl = readFlagValue(args, i, arg);
36
+ i += 1;
37
+ }
38
+ else {
39
+ throw new Error(`Unknown init option: ${arg}`);
40
+ }
41
+ }
42
+ return flags;
43
+ }
44
+ function printInitHelp() {
45
+ console.log(`Usage: feedback-layer-mcp init [options]
46
+
47
+ Options:
48
+ --url <url> Feedback Layer URL. Defaults to ${DEFAULT_API_BASE_URL}
49
+ --token <token> Feedback Layer MCP token
50
+ --project <slug> Project slug, for example findy
51
+ --repo-url <url> Repository URL used for project auto-detection
52
+ -h, --help Show this help
53
+ `);
54
+ }
55
+ async function detectRepoRemote() {
56
+ try {
57
+ const gitConfig = await readFile(".git/config", "utf8");
58
+ const match = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
59
+ return match?.[1]?.trim() ?? null;
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ export async function runInit(args = []) {
66
+ const flags = parseInitFlags(args);
67
+ if (flags.help) {
68
+ printInitHelp();
69
+ return;
70
+ }
71
+ const rl = createInterface({ input, output });
72
+ const existing = await readAuthConfig();
73
+ const apiBaseUrl = flags.apiBaseUrl ||
74
+ (await rl.question(`Feedback Layer URL (${existing.apiBaseUrl ?? DEFAULT_API_BASE_URL}): `)) ||
75
+ existing.apiBaseUrl ||
76
+ DEFAULT_API_BASE_URL;
77
+ const token = flags.token ||
78
+ (await rl.question("Paste Feedback Layer MCP token: ")) ||
79
+ existing.token;
80
+ if (!token)
81
+ throw new Error("Missing Feedback Layer MCP token");
82
+ await writeAuthConfig({ ...existing, apiBaseUrl, token });
83
+ let project = flags.projectSlug
84
+ ? { slug: flags.projectSlug, name: flags.projectSlug }
85
+ : null;
86
+ if (!project) {
87
+ const repoUrl = flags.repoUrl || (await detectRepoRemote());
88
+ const qs = repoUrl ? `?repoUrl=${encodeURIComponent(repoUrl)}` : "";
89
+ const detected = await feedbackLayerApi(`/api/mcp/projects${qs}`);
90
+ project =
91
+ detected.projects[0] ??
92
+ (() => {
93
+ throw new Error("No MCP-enabled project matched this token/repo");
94
+ })();
95
+ }
96
+ await writeAuthConfig({
97
+ ...existing,
98
+ apiBaseUrl,
99
+ token,
100
+ projectSlug: project.slug,
101
+ });
102
+ await writeClaudeConfig(project.slug);
103
+ await writeCodexConfig(project.slug);
104
+ rl.close();
105
+ console.log(`Detected project: ${project.name} (${project.slug})`);
106
+ console.log("Wrote .mcp.json");
107
+ console.log("Updated ~/.codex/config.toml");
108
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runStatus(): Promise<void>;
2
+ export declare function runDoctor(): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ import { resolveProjectRuntimeConfig } from "../client/auth.js";
3
+ export async function runStatus() {
4
+ const config = await resolveProjectRuntimeConfig();
5
+ const result = await feedbackLayerApi(`/api/mcp/tasks?project=${encodeURIComponent(config.projectSlug)}&limit=5`);
6
+ console.log(JSON.stringify({ config: { ...config, token: "[redacted]" }, result }, null, 2));
7
+ }
8
+ export async function runDoctor() {
9
+ const config = await resolveProjectRuntimeConfig();
10
+ await feedbackLayerApi(`/api/mcp/tasks?project=${encodeURIComponent(config.projectSlug)}&limit=1`);
11
+ console.log("Feedback Layer MCP doctor: ok");
12
+ console.log(`API: ${config.apiBaseUrl}`);
13
+ console.log(`Project: ${config.projectSlug}`);
14
+ }
@@ -0,0 +1,6 @@
1
+ type ApiOptions = {
2
+ method?: "GET" | "POST";
3
+ body?: unknown;
4
+ };
5
+ export declare function feedbackLayerApi<T>(path: string, options?: ApiOptions): Promise<T>;
6
+ export {};
@@ -0,0 +1,24 @@
1
+ import { resolveRuntimeConfig } from "./auth.js";
2
+ export async function feedbackLayerApi(path, options = {}) {
3
+ const config = await resolveRuntimeConfig();
4
+ const url = new URL(path, config.apiBaseUrl);
5
+ const res = await fetch(url, {
6
+ method: options.method ?? "GET",
7
+ headers: {
8
+ Authorization: `Bearer ${config.token}`,
9
+ "Content-Type": "application/json",
10
+ },
11
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
12
+ });
13
+ const text = await res.text();
14
+ const data = text ? JSON.parse(text) : {};
15
+ if (!res.ok) {
16
+ const message = typeof data?.message === "string"
17
+ ? data.message
18
+ : typeof data?.error === "string"
19
+ ? data.error
20
+ : `Feedback Layer API ${res.status}`;
21
+ throw new Error(message);
22
+ }
23
+ return data;
24
+ }
@@ -0,0 +1,20 @@
1
+ type AuthConfig = {
2
+ token?: string;
3
+ apiBaseUrl?: string;
4
+ projectSlug?: string;
5
+ };
6
+ export declare const DEFAULT_API_BASE_URL = "https://feedback-layer.venture-ia.com";
7
+ export declare const AUTH_PATH: string;
8
+ export declare function readAuthConfig(): Promise<AuthConfig>;
9
+ export declare function writeAuthConfig(config: AuthConfig): Promise<void>;
10
+ export declare function resolveRuntimeConfig(): Promise<{
11
+ token: string;
12
+ apiBaseUrl: string;
13
+ projectSlug: string | undefined;
14
+ }>;
15
+ export declare function resolveProjectRuntimeConfig(): Promise<{
16
+ projectSlug: string;
17
+ token: string;
18
+ apiBaseUrl: string;
19
+ }>;
20
+ export {};
@@ -0,0 +1,37 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ export const DEFAULT_API_BASE_URL = "https://feedback-layer.venture-ia.com";
5
+ export const AUTH_PATH = join(homedir(), ".config", "feedback-layer", "auth.json");
6
+ export async function readAuthConfig() {
7
+ try {
8
+ const raw = await readFile(AUTH_PATH, "utf8");
9
+ return JSON.parse(raw);
10
+ }
11
+ catch {
12
+ return {};
13
+ }
14
+ }
15
+ export async function writeAuthConfig(config) {
16
+ await mkdir(dirname(AUTH_PATH), { recursive: true });
17
+ await writeFile(AUTH_PATH, `${JSON.stringify(config, null, 2)}\n`, {
18
+ mode: 0o600,
19
+ });
20
+ }
21
+ export async function resolveRuntimeConfig() {
22
+ const file = await readAuthConfig();
23
+ const token = process.env.FL_TOKEN ?? file.token;
24
+ const apiBaseUrl = process.env.FL_API_BASE_URL ?? file.apiBaseUrl ?? DEFAULT_API_BASE_URL;
25
+ const projectSlug = process.env.FL_PROJECT_SLUG ?? file.projectSlug;
26
+ if (!token) {
27
+ throw new Error("Missing Feedback Layer token. Run `feedback-layer-mcp init`.");
28
+ }
29
+ return { token, apiBaseUrl, projectSlug };
30
+ }
31
+ export async function resolveProjectRuntimeConfig() {
32
+ const config = await resolveRuntimeConfig();
33
+ if (!config.projectSlug) {
34
+ throw new Error("Missing project slug. Set FL_PROJECT_SLUG or run init.");
35
+ }
36
+ return { ...config, projectSlug: config.projectSlug };
37
+ }
@@ -0,0 +1 @@
1
+ export declare function writeClaudeConfig(projectSlug: string): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ export async function writeClaudeConfig(projectSlug) {
3
+ const config = {
4
+ mcpServers: {
5
+ "feedback-layer": {
6
+ command: "npx",
7
+ args: ["-y", "feedback-layer-mcp@latest"],
8
+ env: {
9
+ FL_PROJECT_SLUG: projectSlug,
10
+ },
11
+ },
12
+ },
13
+ };
14
+ await writeFile(".mcp.json", `${JSON.stringify(config, null, 2)}\n`);
15
+ }
@@ -0,0 +1 @@
1
+ export declare function writeCodexConfig(projectSlug: string): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
5
+ export async function writeCodexConfig(projectSlug) {
6
+ let current = "";
7
+ try {
8
+ current = await readFile(CODEX_CONFIG, "utf8");
9
+ }
10
+ catch {
11
+ current = "";
12
+ }
13
+ const block = `[mcp_servers.feedback-layer]
14
+ command = "npx"
15
+ args = ["-y", "feedback-layer-mcp@latest"]
16
+
17
+ [mcp_servers.feedback-layer.env]
18
+ FL_PROJECT_SLUG = "${projectSlug}"
19
+ `;
20
+ const withoutExisting = current
21
+ .replace(/\n?\[mcp_servers\.feedback-layer\][\s\S]*?(?=\n\[|\s*$)/g, "")
22
+ .replace(/\n?\[mcp_servers\.feedback-layer\.env\][\s\S]*?(?=\n\[|\s*$)/g, "");
23
+ await mkdir(dirname(CODEX_CONFIG), { recursive: true });
24
+ const nextConfig = `${withoutExisting.trimEnd()}\n\n${block}`.trimStart();
25
+ await writeFile(CODEX_CONFIG, `${nextConfig}\n`);
26
+ }
@@ -0,0 +1,3 @@
1
+ export declare function startHeartbeat(taskId: string, sessionId: string): void;
2
+ export declare function stopHeartbeat(taskId: string): void;
3
+ export declare function releaseActiveClaims(sessionId: string): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { feedbackLayerApi } from "./client/api.js";
2
+ const activeClaims = new Map();
3
+ export function startHeartbeat(taskId, sessionId) {
4
+ stopHeartbeat(taskId);
5
+ const timer = setInterval(async () => {
6
+ try {
7
+ await feedbackLayerApi(`/api/mcp/tasks/${taskId}/heartbeat`, {
8
+ method: "POST",
9
+ body: { sessionId },
10
+ });
11
+ }
12
+ catch (err) {
13
+ console.error(`[feedback-layer] heartbeat failed for ${taskId}`, err);
14
+ stopHeartbeat(taskId);
15
+ }
16
+ }, 60_000);
17
+ activeClaims.set(taskId, timer);
18
+ }
19
+ export function stopHeartbeat(taskId) {
20
+ const timer = activeClaims.get(taskId);
21
+ if (timer)
22
+ clearInterval(timer);
23
+ activeClaims.delete(taskId);
24
+ }
25
+ export async function releaseActiveClaims(sessionId) {
26
+ for (const taskId of activeClaims.keys()) {
27
+ stopHeartbeat(taskId);
28
+ await feedbackLayerApi(`/api/mcp/tasks/${taskId}/release`, {
29
+ method: "POST",
30
+ body: { sessionId },
31
+ }).catch(() => { });
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import os from "node:os";
4
+ import process from "node:process";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
+ import { resolveProjectRuntimeConfig } from "./client/auth.js";
9
+ import { releaseActiveClaims, startHeartbeat, stopHeartbeat } from "./heartbeat.js";
10
+ import { runInit } from "./cli/init.js";
11
+ import { runDoctor, runStatus } from "./cli/status.js";
12
+ import { claimTask, claimTaskTool } from "./tools/claim-task.js";
13
+ import { completeTask, completeTaskTool } from "./tools/complete-task.js";
14
+ import { getTask, getTaskTool } from "./tools/get-task.js";
15
+ import { listTasks, listTasksTool } from "./tools/list-tasks.js";
16
+ import { releaseTask, releaseTaskTool } from "./tools/release-task.js";
17
+ const sessionId = process.env.FL_SESSION_ID ??
18
+ crypto
19
+ .createHash("sha256")
20
+ .update(`${process.cwd()}|${os.hostname()}|${process.pid}`)
21
+ .digest("hex")
22
+ .slice(0, 24);
23
+ const tools = [
24
+ listTasksTool,
25
+ getTaskTool,
26
+ claimTaskTool,
27
+ completeTaskTool,
28
+ releaseTaskTool,
29
+ ];
30
+ function asRecord(value) {
31
+ return value && typeof value === "object"
32
+ ? value
33
+ : {};
34
+ }
35
+ async function callTool(name, args) {
36
+ const config = await resolveProjectRuntimeConfig();
37
+ if (name === "list_tasks")
38
+ return listTasks(args, config.projectSlug);
39
+ if (name === "get_task")
40
+ return getTask(args);
41
+ if (name === "claim_task") {
42
+ const result = await claimTask(args, sessionId);
43
+ if (typeof args.taskId === "string")
44
+ startHeartbeat(args.taskId, sessionId);
45
+ return result;
46
+ }
47
+ if (name === "complete_task") {
48
+ const result = await completeTask(args, sessionId);
49
+ if (typeof args.taskId === "string")
50
+ stopHeartbeat(args.taskId);
51
+ return result;
52
+ }
53
+ if (name === "release_task") {
54
+ const result = await releaseTask(args, sessionId);
55
+ if (typeof args.taskId === "string")
56
+ stopHeartbeat(args.taskId);
57
+ return result;
58
+ }
59
+ throw new Error(`Unknown tool: ${name}`);
60
+ }
61
+ async function startServer() {
62
+ const server = new Server({ name: "feedback-layer", version: "0.1.0" }, { capabilities: { tools: {} } });
63
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
64
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
65
+ const result = await callTool(request.params.name, asRecord(request.params.arguments));
66
+ return {
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text: JSON.stringify(result, null, 2),
71
+ },
72
+ ],
73
+ };
74
+ });
75
+ const transport = new StdioServerTransport();
76
+ await server.connect(transport);
77
+ }
78
+ async function main() {
79
+ const command = process.argv[2];
80
+ if (command === "init")
81
+ return runInit(process.argv.slice(3));
82
+ if (command === "status")
83
+ return runStatus();
84
+ if (command === "doctor")
85
+ return runDoctor();
86
+ if (command === "--help" || command === "-h") {
87
+ console.log(`Usage: feedback-layer-mcp [command]
88
+
89
+ Commands:
90
+ init Configure local auth and MCP client files
91
+ doctor Verify API connectivity for the configured project
92
+ status Print the current configuration and first tasks
93
+
94
+ Run without a command to start the MCP stdio server.
95
+ `);
96
+ return;
97
+ }
98
+ return startServer();
99
+ }
100
+ process.on("SIGINT", () => {
101
+ releaseActiveClaims(sessionId).finally(() => process.exit(0));
102
+ });
103
+ process.on("SIGTERM", () => {
104
+ releaseActiveClaims(sessionId).finally(() => process.exit(0));
105
+ });
106
+ main().catch((err) => {
107
+ console.error(err instanceof Error ? err.message : err);
108
+ process.exit(1);
109
+ });
@@ -0,0 +1,14 @@
1
+ export declare const claimTaskTool: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ taskId: {
8
+ type: string;
9
+ };
10
+ };
11
+ required: string[];
12
+ };
13
+ };
14
+ export declare function claimTask(args: Record<string, unknown>, sessionId: string): Promise<unknown>;
@@ -0,0 +1,18 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ export const claimTaskTool = {
3
+ name: "claim_task",
4
+ description: "Claim a task for this local session. The server locks it for 5 minutes; the MCP server heartbeats while active.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ taskId: { type: "string" },
9
+ },
10
+ required: ["taskId"],
11
+ },
12
+ };
13
+ export async function claimTask(args, sessionId) {
14
+ return feedbackLayerApi(`/api/mcp/tasks/${String(args.taskId)}/claim`, {
15
+ method: "POST",
16
+ body: { sessionId },
17
+ });
18
+ }
@@ -0,0 +1,17 @@
1
+ export declare const completeTaskTool: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ taskId: {
8
+ type: string;
9
+ };
10
+ prUrl: {
11
+ type: string;
12
+ };
13
+ };
14
+ required: string[];
15
+ };
16
+ };
17
+ export declare function completeTask(args: Record<string, unknown>, sessionId: string): Promise<unknown>;
@@ -0,0 +1,19 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ export const completeTaskTool = {
3
+ name: "complete_task",
4
+ description: "Mark the claimed task complete for review. Requires a PR URL and moves the task to `en_review`.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ taskId: { type: "string" },
9
+ prUrl: { type: "string" },
10
+ },
11
+ required: ["taskId", "prUrl"],
12
+ },
13
+ };
14
+ export async function completeTask(args, sessionId) {
15
+ return feedbackLayerApi(`/api/mcp/tasks/${String(args.taskId)}/complete`, {
16
+ method: "POST",
17
+ body: { sessionId, prUrl: String(args.prUrl) },
18
+ });
19
+ }
@@ -0,0 +1,14 @@
1
+ export declare const getTaskTool: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ taskId: {
8
+ type: string;
9
+ };
10
+ };
11
+ required: string[];
12
+ };
13
+ };
14
+ export declare function getTask(args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,15 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ export const getTaskTool = {
3
+ name: "get_task",
4
+ description: "Get a Feedback Layer task by id, including the generated implementation prompt and acceptance criteria.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ taskId: { type: "string" },
9
+ },
10
+ required: ["taskId"],
11
+ },
12
+ };
13
+ export async function getTask(args) {
14
+ return feedbackLayerApi(`/api/mcp/tasks/${String(args.taskId)}`);
15
+ }
@@ -0,0 +1,19 @@
1
+ export declare const listTasksTool: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ limit: {
8
+ type: string;
9
+ default: number;
10
+ maximum: number;
11
+ };
12
+ minConfidence: {
13
+ type: string;
14
+ default: number;
15
+ };
16
+ };
17
+ };
18
+ };
19
+ export declare function listTasks(args: Record<string, unknown>, projectSlug: string): Promise<unknown>;
@@ -0,0 +1,22 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ export const listTasksTool = {
3
+ name: "list_tasks",
4
+ description: "List ready Feedback Layer tasks for the configured project. Returns unclaimed `pret_ia` tasks sorted by priority.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ limit: { type: "integer", default: 10, maximum: 50 },
9
+ minConfidence: { type: "number", default: 0.5 },
10
+ },
11
+ },
12
+ };
13
+ export async function listTasks(args, projectSlug) {
14
+ const limit = Number(args.limit ?? 10);
15
+ const minConfidence = Number(args.minConfidence ?? 0.5);
16
+ const qs = new URLSearchParams({
17
+ project: projectSlug,
18
+ limit: String(limit),
19
+ minConfidence: String(minConfidence),
20
+ });
21
+ return feedbackLayerApi(`/api/mcp/tasks?${qs.toString()}`);
22
+ }
@@ -0,0 +1,14 @@
1
+ export declare const releaseTaskTool: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ taskId: {
8
+ type: string;
9
+ };
10
+ };
11
+ required: string[];
12
+ };
13
+ };
14
+ export declare function releaseTask(args: Record<string, unknown>, sessionId: string): Promise<unknown>;
@@ -0,0 +1,18 @@
1
+ import { feedbackLayerApi } from "../client/api.js";
2
+ export const releaseTaskTool = {
3
+ name: "release_task",
4
+ description: "Release the current claim for a task and return it to the ready queue if it is still MCP-owned.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ taskId: { type: "string" },
9
+ },
10
+ required: ["taskId"],
11
+ },
12
+ };
13
+ export async function releaseTask(args, sessionId) {
14
+ return feedbackLayerApi(`/api/mcp/tasks/${String(args.taskId)}/release`, {
15
+ method: "POST",
16
+ body: { sessionId },
17
+ });
18
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "feedback-layer-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Feedback Layer MCP server for exposing selected feedback requests to coding agents.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/VentureIA/feedback-layer.git",
8
+ "directory": "packages/mcp"
9
+ },
10
+ "homepage": "https://github.com/VentureIA/feedback-layer#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/VentureIA/feedback-layer/issues"
13
+ },
14
+ "license": "UNLICENSED",
15
+ "mcpName": "io.github.ventureia/feedback-layer-mcp",
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ "./package.json": "./package.json",
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "bin": {
27
+ "feedback-layer-mcp": "dist/index.js",
28
+ "fl-mcp": "dist/index.js"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "server.json",
34
+ "package.json"
35
+ ],
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.json",
44
+ "dev": "tsx src/index.ts",
45
+ "doctor": "node dist/index.js doctor",
46
+ "prepack": "npm run build",
47
+ "start": "node dist/index.js"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.29.0"
51
+ },
52
+ "devDependencies": {
53
+ "tsx": "^4.19.2",
54
+ "typescript": "^5"
55
+ }
56
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.ventureia/feedback-layer-mcp",
4
+ "description": "Feedback Layer MCP server for exposing selected feedback requests to coding agents.",
5
+ "repository": {
6
+ "url": "https://github.com/VentureIA/feedback-layer",
7
+ "source": "github"
8
+ },
9
+ "version": "0.1.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "feedback-layer-mcp",
14
+ "version": "0.1.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }