feedback-layer-mcp 0.1.0 → 0.1.1
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 +15 -5
- package/dist/cli/init.js +8 -20
- package/dist/client/api.js +6 -0
- package/dist/client/auth.js +31 -1
- package/dist/config/codex.d.ts +1 -1
- package/dist/config/codex.js +1 -4
- package/dist/config/project.d.ts +10 -0
- package/dist/config/project.js +68 -0
- package/dist/index.js +1 -1
- package/dist/tools/get-task.d.ts +16 -1
- package/dist/tools/get-task.js +28 -4
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -16,8 +16,17 @@ The init command stores credentials in:
|
|
|
16
16
|
~/.config/feedback-layer/auth.json
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
It also writes
|
|
20
|
-
|
|
19
|
+
It also writes the project binding for the current repo in:
|
|
20
|
+
|
|
21
|
+
```txt
|
|
22
|
+
.feedback-layer/project.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That means the same token can be authorized for several projects, while each
|
|
26
|
+
workspace still resolves to exactly one active project.
|
|
27
|
+
|
|
28
|
+
Init also writes local MCP client configuration for Claude-style `.mcp.json`
|
|
29
|
+
and Codex `~/.codex/config.toml`.
|
|
21
30
|
|
|
22
31
|
## Standard MCP Config
|
|
23
32
|
|
|
@@ -97,6 +106,7 @@ The short alias `fl-mcp` is also available.
|
|
|
97
106
|
|
|
98
107
|
## Security
|
|
99
108
|
|
|
100
|
-
Do not commit tokens
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
Do not commit tokens. Prefer `init`, which stores the token in a user-local file
|
|
110
|
+
with `0600` permissions. Task APIs are scoped to the active project resolved
|
|
111
|
+
from `FL_PROJECT_SLUG`, `.feedback-layer/project.json`, or the local `.mcp.json`
|
|
112
|
+
for the current workspace.
|
package/dist/cli/init.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
1
|
import { createInterface } from "node:readline/promises";
|
|
3
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
3
|
import { feedbackLayerApi } from "../client/api.js";
|
|
5
4
|
import { DEFAULT_API_BASE_URL, readAuthConfig, writeAuthConfig, } from "../client/auth.js";
|
|
6
5
|
import { writeClaudeConfig } from "../config/claude-code.js";
|
|
7
6
|
import { writeCodexConfig } from "../config/codex.js";
|
|
7
|
+
import { detectRepoRemote, writeLocalProjectConfig } from "../config/project.js";
|
|
8
8
|
function readFlagValue(args, index, flag) {
|
|
9
9
|
const value = args[index + 1];
|
|
10
10
|
if (!value || value.startsWith("--")) {
|
|
@@ -52,16 +52,6 @@ Options:
|
|
|
52
52
|
-h, --help Show this help
|
|
53
53
|
`);
|
|
54
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
55
|
export async function runInit(args = []) {
|
|
66
56
|
const flags = parseInitFlags(args);
|
|
67
57
|
if (flags.help) {
|
|
@@ -70,6 +60,7 @@ export async function runInit(args = []) {
|
|
|
70
60
|
}
|
|
71
61
|
const rl = createInterface({ input, output });
|
|
72
62
|
const existing = await readAuthConfig();
|
|
63
|
+
const authConfig = { ...existing, projectSlug: undefined };
|
|
73
64
|
const apiBaseUrl = flags.apiBaseUrl ||
|
|
74
65
|
(await rl.question(`Feedback Layer URL (${existing.apiBaseUrl ?? DEFAULT_API_BASE_URL}): `)) ||
|
|
75
66
|
existing.apiBaseUrl ||
|
|
@@ -79,12 +70,12 @@ export async function runInit(args = []) {
|
|
|
79
70
|
existing.token;
|
|
80
71
|
if (!token)
|
|
81
72
|
throw new Error("Missing Feedback Layer MCP token");
|
|
82
|
-
await writeAuthConfig({ ...
|
|
73
|
+
await writeAuthConfig({ ...authConfig, apiBaseUrl, token });
|
|
74
|
+
const repoUrl = flags.repoUrl || (await detectRepoRemote());
|
|
83
75
|
let project = flags.projectSlug
|
|
84
76
|
? { slug: flags.projectSlug, name: flags.projectSlug }
|
|
85
77
|
: null;
|
|
86
78
|
if (!project) {
|
|
87
|
-
const repoUrl = flags.repoUrl || (await detectRepoRemote());
|
|
88
79
|
const qs = repoUrl ? `?repoUrl=${encodeURIComponent(repoUrl)}` : "";
|
|
89
80
|
const detected = await feedbackLayerApi(`/api/mcp/projects${qs}`);
|
|
90
81
|
project =
|
|
@@ -93,16 +84,13 @@ export async function runInit(args = []) {
|
|
|
93
84
|
throw new Error("No MCP-enabled project matched this token/repo");
|
|
94
85
|
})();
|
|
95
86
|
}
|
|
96
|
-
await writeAuthConfig({
|
|
97
|
-
|
|
98
|
-
apiBaseUrl,
|
|
99
|
-
token,
|
|
100
|
-
projectSlug: project.slug,
|
|
101
|
-
});
|
|
87
|
+
await writeAuthConfig({ ...authConfig, apiBaseUrl, token });
|
|
88
|
+
await writeLocalProjectConfig({ projectSlug: project.slug, repoUrl });
|
|
102
89
|
await writeClaudeConfig(project.slug);
|
|
103
|
-
await writeCodexConfig(
|
|
90
|
+
await writeCodexConfig();
|
|
104
91
|
rl.close();
|
|
105
92
|
console.log(`Detected project: ${project.name} (${project.slug})`);
|
|
93
|
+
console.log("Wrote .feedback-layer/project.json");
|
|
106
94
|
console.log("Wrote .mcp.json");
|
|
107
95
|
console.log("Updated ~/.codex/config.toml");
|
|
108
96
|
}
|
package/dist/client/api.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { resolveRuntimeConfig } from "./auth.js";
|
|
2
|
+
function isTaskEndpoint(pathname) {
|
|
3
|
+
return pathname === "/api/mcp/tasks" || pathname.startsWith("/api/mcp/tasks/");
|
|
4
|
+
}
|
|
2
5
|
export async function feedbackLayerApi(path, options = {}) {
|
|
3
6
|
const config = await resolveRuntimeConfig();
|
|
4
7
|
const url = new URL(path, config.apiBaseUrl);
|
|
8
|
+
if (config.projectSlug && isTaskEndpoint(url.pathname)) {
|
|
9
|
+
url.searchParams.set("project", config.projectSlug);
|
|
10
|
+
}
|
|
5
11
|
const res = await fetch(url, {
|
|
6
12
|
method: options.method ?? "GET",
|
|
7
13
|
headers: {
|
package/dist/client/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { detectRepoRemote, normalizeRepoUrl, readLocalProjectConfig, } from "../config/project.js";
|
|
4
5
|
export const DEFAULT_API_BASE_URL = "https://feedback-layer.venture-ia.com";
|
|
5
6
|
export const AUTH_PATH = join(homedir(), ".config", "feedback-layer", "auth.json");
|
|
6
7
|
export async function readAuthConfig() {
|
|
@@ -18,11 +19,40 @@ export async function writeAuthConfig(config) {
|
|
|
18
19
|
mode: 0o600,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
22
|
+
async function readProjectSlugFromMcpConfig() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = await readFile(join(process.cwd(), ".mcp.json"), "utf8");
|
|
25
|
+
const config = JSON.parse(raw);
|
|
26
|
+
return config.mcpServers?.["feedback-layer"]?.env?.FL_PROJECT_SLUG;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function resolveProjectSlug() {
|
|
33
|
+
if (process.env.FL_PROJECT_SLUG)
|
|
34
|
+
return process.env.FL_PROJECT_SLUG;
|
|
35
|
+
let local = null;
|
|
36
|
+
try {
|
|
37
|
+
local = await readLocalProjectConfig();
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
if (local?.repoUrl) {
|
|
41
|
+
const currentRepoUrl = await detectRepoRemote();
|
|
42
|
+
if (!currentRepoUrl ||
|
|
43
|
+
normalizeRepoUrl(currentRepoUrl) !== normalizeRepoUrl(local.repoUrl)) {
|
|
44
|
+
throw new Error("Feedback Layer project config does not match this git remote. Run `feedback-layer-mcp init` in this workspace.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (local?.projectSlug)
|
|
48
|
+
return local.projectSlug;
|
|
49
|
+
return await readProjectSlugFromMcpConfig();
|
|
50
|
+
}
|
|
21
51
|
export async function resolveRuntimeConfig() {
|
|
22
52
|
const file = await readAuthConfig();
|
|
23
53
|
const token = process.env.FL_TOKEN ?? file.token;
|
|
24
54
|
const apiBaseUrl = process.env.FL_API_BASE_URL ?? file.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
25
|
-
const projectSlug =
|
|
55
|
+
const projectSlug = await resolveProjectSlug();
|
|
26
56
|
if (!token) {
|
|
27
57
|
throw new Error("Missing Feedback Layer token. Run `feedback-layer-mcp init`.");
|
|
28
58
|
}
|
package/dist/config/codex.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function writeCodexConfig(
|
|
1
|
+
export declare function writeCodexConfig(): Promise<void>;
|
package/dist/config/codex.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
const CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
|
|
5
|
-
export async function writeCodexConfig(
|
|
5
|
+
export async function writeCodexConfig() {
|
|
6
6
|
let current = "";
|
|
7
7
|
try {
|
|
8
8
|
current = await readFile(CODEX_CONFIG, "utf8");
|
|
@@ -13,9 +13,6 @@ export async function writeCodexConfig(projectSlug) {
|
|
|
13
13
|
const block = `[mcp_servers.feedback-layer]
|
|
14
14
|
command = "npx"
|
|
15
15
|
args = ["-y", "feedback-layer-mcp@latest"]
|
|
16
|
-
|
|
17
|
-
[mcp_servers.feedback-layer.env]
|
|
18
|
-
FL_PROJECT_SLUG = "${projectSlug}"
|
|
19
16
|
`;
|
|
20
17
|
const withoutExisting = current
|
|
21
18
|
.replace(/\n?\[mcp_servers\.feedback-layer\][\s\S]*?(?=\n\[|\s*$)/g, "")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type LocalProjectConfig = {
|
|
2
|
+
projectSlug: string;
|
|
3
|
+
repoUrl?: string | null;
|
|
4
|
+
};
|
|
5
|
+
export declare const LOCAL_PROJECT_CONFIG: string;
|
|
6
|
+
export declare function normalizeRepoUrl(value: string): string;
|
|
7
|
+
export declare function findGitRoot(start?: string): Promise<string | null>;
|
|
8
|
+
export declare function detectRepoRemote(start?: string): Promise<string | null>;
|
|
9
|
+
export declare function readLocalProjectConfig(): Promise<LocalProjectConfig>;
|
|
10
|
+
export declare function writeLocalProjectConfig(config: LocalProjectConfig): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, parse } from "node:path";
|
|
3
|
+
const PROJECT_CONFIG_RELATIVE = join(".feedback-layer", "project.json");
|
|
4
|
+
export const LOCAL_PROJECT_CONFIG = join(process.cwd(), PROJECT_CONFIG_RELATIVE);
|
|
5
|
+
async function findUp(relativePath, start = process.cwd()) {
|
|
6
|
+
let current = start;
|
|
7
|
+
while (true) {
|
|
8
|
+
const candidate = join(current, relativePath);
|
|
9
|
+
try {
|
|
10
|
+
await readFile(candidate, "utf8");
|
|
11
|
+
return candidate;
|
|
12
|
+
}
|
|
13
|
+
catch { }
|
|
14
|
+
const parent = dirname(current);
|
|
15
|
+
if (parent === current || current === parse(current).root)
|
|
16
|
+
return null;
|
|
17
|
+
current = parent;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function parseOriginRemote(gitConfig) {
|
|
21
|
+
const match = gitConfig.match(/\[remote "origin"\][\s\S]*?url = (.+)/);
|
|
22
|
+
return match?.[1]?.trim() ?? null;
|
|
23
|
+
}
|
|
24
|
+
export function normalizeRepoUrl(value) {
|
|
25
|
+
return value
|
|
26
|
+
.trim()
|
|
27
|
+
.replace(/^git@([^:]+):(.+)$/, "https://$1/$2")
|
|
28
|
+
.replace(/\.git$/, "")
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
export async function findGitRoot(start = process.cwd()) {
|
|
32
|
+
let current = start;
|
|
33
|
+
while (true) {
|
|
34
|
+
try {
|
|
35
|
+
await readFile(join(current, ".git", "config"), "utf8");
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
const parent = dirname(current);
|
|
40
|
+
if (parent === current || current === parse(current).root)
|
|
41
|
+
return null;
|
|
42
|
+
current = parent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function detectRepoRemote(start = process.cwd()) {
|
|
46
|
+
const gitRoot = await findGitRoot(start);
|
|
47
|
+
if (!gitRoot)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
return parseOriginRemote(await readFile(join(gitRoot, ".git", "config"), "utf8"));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function readLocalProjectConfig() {
|
|
57
|
+
const configPath = await findUp(PROJECT_CONFIG_RELATIVE);
|
|
58
|
+
if (!configPath)
|
|
59
|
+
throw new Error("Missing local Feedback Layer project config");
|
|
60
|
+
const raw = await readFile(configPath, "utf8");
|
|
61
|
+
return JSON.parse(raw);
|
|
62
|
+
}
|
|
63
|
+
export async function writeLocalProjectConfig(config) {
|
|
64
|
+
const gitRoot = await findGitRoot();
|
|
65
|
+
const configPath = join(gitRoot ?? process.cwd(), PROJECT_CONFIG_RELATIVE);
|
|
66
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
67
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
68
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ async function callTool(name, args) {
|
|
|
59
59
|
throw new Error(`Unknown tool: ${name}`);
|
|
60
60
|
}
|
|
61
61
|
async function startServer() {
|
|
62
|
-
const server = new Server({ name: "feedback-layer", version: "0.1.
|
|
62
|
+
const server = new Server({ name: "feedback-layer", version: "0.1.1" }, { capabilities: { tools: {} } });
|
|
63
63
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
64
64
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
65
65
|
const result = await callTool(request.params.name, asRecord(request.params.arguments));
|
package/dist/tools/get-task.d.ts
CHANGED
|
@@ -6,9 +6,24 @@ export declare const getTaskTool: {
|
|
|
6
6
|
properties: {
|
|
7
7
|
taskId: {
|
|
8
8
|
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
id: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
feedbackId: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
code: {
|
|
20
|
+
type: string;
|
|
21
|
+
description: string;
|
|
9
22
|
};
|
|
10
23
|
};
|
|
11
|
-
|
|
24
|
+
anyOf: {
|
|
25
|
+
required: string[];
|
|
26
|
+
}[];
|
|
12
27
|
};
|
|
13
28
|
};
|
|
14
29
|
export declare function getTask(args: Record<string, unknown>): Promise<unknown>;
|
package/dist/tools/get-task.js
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
import { feedbackLayerApi } from "../client/api.js";
|
|
2
2
|
export const getTaskTool = {
|
|
3
3
|
name: "get_task",
|
|
4
|
-
description: "Get a Feedback Layer task by id, including the generated implementation prompt and acceptance criteria.",
|
|
4
|
+
description: "Get a Feedback Layer task by task id, feedback id, or visible feedback code, including the generated implementation prompt and acceptance criteria.",
|
|
5
5
|
inputSchema: {
|
|
6
6
|
type: "object",
|
|
7
7
|
properties: {
|
|
8
|
-
taskId: {
|
|
8
|
+
taskId: {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "Generated task id returned by list_tasks.",
|
|
11
|
+
},
|
|
12
|
+
id: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Alias for taskId.",
|
|
15
|
+
},
|
|
16
|
+
feedbackId: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Feedback id returned by list_tasks.",
|
|
19
|
+
},
|
|
20
|
+
code: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Human-readable feedback code, for example FB-253.",
|
|
23
|
+
},
|
|
9
24
|
},
|
|
10
|
-
|
|
25
|
+
anyOf: [
|
|
26
|
+
{ required: ["taskId"] },
|
|
27
|
+
{ required: ["id"] },
|
|
28
|
+
{ required: ["feedbackId"] },
|
|
29
|
+
{ required: ["code"] },
|
|
30
|
+
],
|
|
11
31
|
},
|
|
12
32
|
};
|
|
13
33
|
export async function getTask(args) {
|
|
14
|
-
|
|
34
|
+
const identifier = args.taskId ?? args.id ?? args.feedbackId ?? args.code;
|
|
35
|
+
if (typeof identifier !== "string" || identifier.trim().length === 0) {
|
|
36
|
+
throw new Error("Missing task identifier. Pass taskId, feedbackId, or code.");
|
|
37
|
+
}
|
|
38
|
+
return feedbackLayerApi(`/api/mcp/tasks/${encodeURIComponent(identifier.trim())}`);
|
|
15
39
|
}
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/VentureIA/feedback-layer",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "feedback-layer-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|