@fml-inc/fml 0.7.0 → 0.8.0-rc.2
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/.claude-plugin/plugin.json +2 -4
- package/README.md +323 -79
- package/agent-assets/fml/SKILL.md +180 -0
- package/bin/fml +4 -18
- package/bin/hook-handler +7 -0
- package/bin/mcp-server +4 -18
- package/bin/observer +6 -0
- package/dist/chunk-3KISRT57.js +140 -0
- package/dist/chunk-56TPYMNK.js +4657 -0
- package/dist/chunk-7V7G7NVF.js +142 -0
- package/dist/chunk-A6HAEVJX.js +1606 -0
- package/dist/chunk-DZKYW327.js +1037 -0
- package/dist/chunk-ENW2GJNO.js +109 -0
- package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
- package/dist/{chunk-PTUZMKSN.js → chunk-R45CWWZO.js} +34 -23
- package/dist/chunk-WZLLO563.js +953 -0
- package/dist/chunk-YSU5CS2V.js +3331 -0
- package/dist/cli.js +7742 -1072
- package/dist/{esm-2EUDFZWP.js → esm-4YKLKFRX.js} +546 -741
- package/dist/hook-handler.js +440 -0
- package/dist/mcp/server.js +112 -98
- package/dist/observer-server.js +1566 -0
- package/dist/sync-cycle.js +31 -0
- package/dist/{token-store-3IQSLOTQ.js → token-store-BT5THIAJ.js} +10 -3
- package/hooks/hooks.json +290 -0
- package/package.json +24 -27
- package/dist/chunk-PTUZMKSN.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S4TJNLEN.js +0 -61
- package/dist/chunk-S4TJNLEN.js.map +0 -1
- package/dist/chunk-VV4JHH4H.js +0 -437
- package/dist/chunk-VV4JHH4H.js.map +0 -1
- package/dist/chunk-YH5ROR7G.js +0 -69
- package/dist/chunk-YH5ROR7G.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/daemon-MSLDHLMH.js +0 -21
- package/dist/daemon-MSLDHLMH.js.map +0 -1
- package/dist/daemon-utils-OODBIYB7.js +0 -10
- package/dist/daemon-utils-OODBIYB7.js.map +0 -1
- package/dist/esm-2EUDFZWP.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/postinstall.js +0 -16
- package/dist/postinstall.js.map +0 -1
- package/dist/token-store-3IQSLOTQ.js.map +0 -1
- package/scripts/postinstall.cjs +0 -25
package/dist/mcp/server.js
CHANGED
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createFmlClient,
|
|
4
4
|
getAuthenticatedClient
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-R45CWWZO.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveRepoFromCwd
|
|
8
|
+
} from "../chunk-7V7G7NVF.js";
|
|
6
9
|
import {
|
|
7
10
|
FML_DATA_DIR,
|
|
8
11
|
FML_LOG_DIR,
|
|
9
12
|
Sentry,
|
|
10
|
-
|
|
13
|
+
authStorePathFor,
|
|
14
|
+
getActiveEnv,
|
|
11
15
|
getSelectedOrg,
|
|
16
|
+
getSiteUrlForConvexUrl,
|
|
12
17
|
getValidToken,
|
|
13
18
|
initSentry,
|
|
14
19
|
readTokens
|
|
15
|
-
} from "../chunk-
|
|
16
|
-
import
|
|
17
|
-
parsePanopticonRunning
|
|
18
|
-
} from "../chunk-S4TJNLEN.js";
|
|
19
|
-
import "../chunk-YH5ROR7G.js";
|
|
20
|
-
import "../chunk-PZ5AY32C.js";
|
|
20
|
+
} from "../chunk-A6HAEVJX.js";
|
|
21
|
+
import "../chunk-MLKGABMK.js";
|
|
21
22
|
|
|
22
23
|
// src/mcp/server.ts
|
|
23
24
|
import fs from "fs";
|
|
@@ -27,7 +28,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
27
28
|
|
|
28
29
|
// src/mcp/tools.ts
|
|
29
30
|
import { z } from "zod";
|
|
30
|
-
import { resolveRepoFromCwd } from "@fml-inc/panopticon/repo";
|
|
31
31
|
function textResult(data) {
|
|
32
32
|
return {
|
|
33
33
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
@@ -39,38 +39,76 @@ function errorResult(message) {
|
|
|
39
39
|
isError: true
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
function restrictedSessionToolError(toolName, args) {
|
|
43
|
+
const allowedSessionId = process.env.FML_MCP_ALLOWED_SESSION_ID;
|
|
44
|
+
if (allowedSessionId === void 0) return null;
|
|
45
|
+
if (toolName !== "get-session-timeline" || args.sessionId !== allowedSessionId) {
|
|
46
|
+
return errorResult(
|
|
47
|
+
"This system task is restricted to its assigned session timeline."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
async function toolHandler(toolName, args, environment) {
|
|
44
53
|
try {
|
|
45
|
-
const
|
|
54
|
+
const restrictionError = restrictedSessionToolError(toolName, args);
|
|
55
|
+
if (restrictionError) return restrictionError;
|
|
56
|
+
const api = await getAuthenticatedClient(environment);
|
|
46
57
|
if (!api) {
|
|
47
58
|
return errorResult(
|
|
48
|
-
"Not authenticated. Run `fml login` to sign in, then restart Claude Code."
|
|
59
|
+
"Not authenticated. Run `fml login` to sign in, then restart Claude Code."
|
|
49
60
|
);
|
|
50
61
|
}
|
|
51
62
|
const result = await api.callBackend(toolName, args);
|
|
52
63
|
if (!result.ok) {
|
|
53
|
-
return errorResult(
|
|
64
|
+
return errorResult(result.error ?? "Unknown error");
|
|
54
65
|
}
|
|
55
66
|
return textResult(result.result);
|
|
56
67
|
} catch (err) {
|
|
57
68
|
Sentry.captureException(err);
|
|
58
69
|
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
-
return errorResult(`Unexpected error: ${msg}`
|
|
70
|
+
return errorResult(`Unexpected error: ${msg}`);
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
|
-
function registerTools(server2) {
|
|
73
|
+
function registerTools(server2, environment = getActiveEnv()) {
|
|
74
|
+
const handleTool = (toolName, args) => toolHandler(toolName, args, environment);
|
|
75
|
+
const registerSessionTimelineTool = () => {
|
|
76
|
+
server2.tool(
|
|
77
|
+
"get_session_timeline",
|
|
78
|
+
'Get chronological timeline for a coding agent session. Returns messages, hook events, and transcript-derived scanner events interleaved by timestamp. Use source="events" for the scanner event stream (thinking, tool_call, permission_mode, stop_hook_summary, ...); source="hooks" only returns live hook-capture events and is empty for scanner-only sessions. REQUIRES: sessionId from list_engineering_sessions.',
|
|
79
|
+
{
|
|
80
|
+
sessionId: z.string().describe("The session ID to get details for."),
|
|
81
|
+
includeSystemMessages: z.boolean().optional().describe("Include system messages (default false)."),
|
|
82
|
+
source: z.enum(["messages", "hooks", "events", "all"]).optional().describe(
|
|
83
|
+
'Filter by source: "messages" (scanner messages only), "hooks" (live hook-capture events only), "events" (transcript-derived scanner events), "all" (everything interleaved). Default "all".'
|
|
84
|
+
),
|
|
85
|
+
eventType: z.string().optional().describe(
|
|
86
|
+
'Filter hook/scanner events by type (hook: "PermissionRequest", "PreToolUse", "Stop"; scanner: "tool_call", "permission_mode", "stop_hook_summary"). Only applies when source includes hooks or events.'
|
|
87
|
+
),
|
|
88
|
+
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
89
|
+
offset: z.number().optional().describe("Skip first N entries for pagination.")
|
|
90
|
+
},
|
|
91
|
+
async (args) => handleTool("get-session-timeline", args)
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
if (process.env.FML_MCP_ALLOWED_SESSION_ID !== void 0) {
|
|
95
|
+
registerSessionTimelineTool();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
63
98
|
server2.tool(
|
|
64
99
|
"whoami",
|
|
65
100
|
"Show current authenticated user and available organizations",
|
|
66
101
|
{},
|
|
67
102
|
async () => {
|
|
68
|
-
const tokens = readTokens();
|
|
69
|
-
const token = tokens ? await getValidToken() : null;
|
|
103
|
+
const tokens = readTokens(environment.name);
|
|
104
|
+
const token = tokens ? await getValidToken({ env: environment.name }) : null;
|
|
70
105
|
let orgs = null;
|
|
71
106
|
if (token) {
|
|
72
107
|
try {
|
|
73
|
-
orgs = await createFmlClient(token
|
|
108
|
+
orgs = await createFmlClient(token, {
|
|
109
|
+
envName: environment.name,
|
|
110
|
+
siteUrl: getSiteUrlForConvexUrl(environment.convexUrl)
|
|
111
|
+
}).queryOrgs();
|
|
74
112
|
} catch {
|
|
75
113
|
}
|
|
76
114
|
}
|
|
@@ -96,7 +134,7 @@ function registerTools(server2) {
|
|
|
96
134
|
),
|
|
97
135
|
cursor: z.string().optional().describe("Pagination cursor from previous result to get next page")
|
|
98
136
|
},
|
|
99
|
-
async (args) =>
|
|
137
|
+
async (args) => handleTool("list-messages", args)
|
|
100
138
|
);
|
|
101
139
|
server2.tool(
|
|
102
140
|
"fml_list_messages_with_context",
|
|
@@ -110,7 +148,7 @@ function registerTools(server2) {
|
|
|
110
148
|
"Number of messages to retrieve after the target (default: 5)"
|
|
111
149
|
)
|
|
112
150
|
},
|
|
113
|
-
async (args) =>
|
|
151
|
+
async (args) => handleTool("list-messages-with-context", args)
|
|
114
152
|
);
|
|
115
153
|
server2.tool(
|
|
116
154
|
"fml_get_slack_channel_history",
|
|
@@ -121,7 +159,7 @@ function registerTools(server2) {
|
|
|
121
159
|
),
|
|
122
160
|
limit: z.number().optional().describe("Number of messages to fetch (default 20, max 50)")
|
|
123
161
|
},
|
|
124
|
-
async (args) =>
|
|
162
|
+
async (args) => handleTool("get-slack-channel-history", args)
|
|
125
163
|
);
|
|
126
164
|
server2.tool(
|
|
127
165
|
"fml_get_slack_message",
|
|
@@ -140,13 +178,13 @@ function registerTools(server2) {
|
|
|
140
178
|
"If true, fetches thread replies along with the message (default false)"
|
|
141
179
|
)
|
|
142
180
|
},
|
|
143
|
-
async (args) =>
|
|
181
|
+
async (args) => handleTool("get-slack-message", args)
|
|
144
182
|
);
|
|
145
183
|
server2.tool(
|
|
146
184
|
"fml_list_skills",
|
|
147
185
|
"List all available FML skills. Skills provide detailed knowledge about FML features.",
|
|
148
186
|
{},
|
|
149
|
-
async () =>
|
|
187
|
+
async () => handleTool("list-skills", {})
|
|
150
188
|
);
|
|
151
189
|
server2.tool(
|
|
152
190
|
"fml_load_skill",
|
|
@@ -154,7 +192,7 @@ function registerTools(server2) {
|
|
|
154
192
|
{
|
|
155
193
|
skillId: z.string().describe("The skill ID to load (e.g., 'fml-help', 'fml-navigation')")
|
|
156
194
|
},
|
|
157
|
-
async (args) =>
|
|
195
|
+
async (args) => handleTool("load-skill", args)
|
|
158
196
|
);
|
|
159
197
|
server2.tool(
|
|
160
198
|
"fml_search_analysis",
|
|
@@ -172,7 +210,7 @@ function registerTools(server2) {
|
|
|
172
210
|
"Limit to one repository. Omit to search every repo in the caller's organization."
|
|
173
211
|
)
|
|
174
212
|
},
|
|
175
|
-
async (args) =>
|
|
213
|
+
async (args) => handleTool("search-analysis", args)
|
|
176
214
|
);
|
|
177
215
|
server2.tool(
|
|
178
216
|
"fml_run_analysis_workflow",
|
|
@@ -197,23 +235,13 @@ function registerTools(server2) {
|
|
|
197
235
|
"Target repository ID. Omit for single-repo orgs (auto-pick). Required when the org has multiple repos \u2014 the tool will list them in its error if not provided."
|
|
198
236
|
)
|
|
199
237
|
},
|
|
200
|
-
async (args) =>
|
|
201
|
-
);
|
|
202
|
-
server2.tool(
|
|
203
|
-
"fml_run_team_analysis",
|
|
204
|
-
"Run a team-wide AI coding practice analysis for the caller's organization. Produces a structured report with per-person narratives covering tool usage, collaboration patterns, configuration, and notable changes. Use for prompts like 'run team analysis for the last 14 days' or 'how is the team doing?'. Kicks a durable Convex workflow and returns immediately with a reportId; when called inside a conversation, a completion message is posted back to that conversation when the workflow finishes \u2014 wait for that follow-up turn, do not poll. Idempotent: if a run is already in flight, returns the existing reportId with started=false. Requires org-owner access and at least one running dev environment.",
|
|
205
|
-
{
|
|
206
|
-
windowDays: z.number().int().min(1).max(90).optional().describe(
|
|
207
|
-
"Size of the analysis window in days. Defaults to 30. Clamped to [1, 90]. Match the user's phrasing: 'last 14 days' \u2192 14, 'last month' \u2192 30, 'this week' \u2192 7."
|
|
208
|
-
)
|
|
209
|
-
},
|
|
210
|
-
async (args) => toolHandler("run-team-analysis", args)
|
|
238
|
+
async (args) => handleTool("run-analysis-workflow", args)
|
|
211
239
|
);
|
|
212
240
|
server2.tool(
|
|
213
241
|
"fml_list_integrations",
|
|
214
242
|
"List integrations for this project \u2014 both connected and available. Use to check what integrations are active.",
|
|
215
243
|
{},
|
|
216
|
-
async () =>
|
|
244
|
+
async () => handleTool("list-integrations", {})
|
|
217
245
|
);
|
|
218
246
|
const integrationTools = [
|
|
219
247
|
{
|
|
@@ -271,7 +299,7 @@ function registerTools(server2) {
|
|
|
271
299
|
"Optional project ID to scope this query. Required for GitHub (needs repo context)."
|
|
272
300
|
)
|
|
273
301
|
},
|
|
274
|
-
async (args) =>
|
|
302
|
+
async (args) => handleTool(tool.mastraId, args)
|
|
275
303
|
);
|
|
276
304
|
}
|
|
277
305
|
server2.tool(
|
|
@@ -285,7 +313,7 @@ function registerTools(server2) {
|
|
|
285
313
|
scope: z.enum(["org", "project", "user"]).optional().describe("Query scope. 'org' (default), 'project', or 'user'."),
|
|
286
314
|
githubUsername: z.string().optional().describe("Required when scope is 'user'. GitHub username.")
|
|
287
315
|
},
|
|
288
|
-
async (args) =>
|
|
316
|
+
async (args) => handleTool("get-engineering-activity", args)
|
|
289
317
|
);
|
|
290
318
|
server2.tool(
|
|
291
319
|
"list_engineering_sessions",
|
|
@@ -299,25 +327,9 @@ function registerTools(server2) {
|
|
|
299
327
|
until: z.string().optional().describe("ISO 8601 end timestamp."),
|
|
300
328
|
limit: z.number().optional().describe("Max sessions to return (default 20, max 50).")
|
|
301
329
|
},
|
|
302
|
-
async (args) =>
|
|
303
|
-
);
|
|
304
|
-
server2.tool(
|
|
305
|
-
"get_session_timeline",
|
|
306
|
-
'Get chronological timeline for a coding agent session. Returns messages and/or hook events interleaved by timestamp. Use source="hooks" to see permission requests, tool approvals, etc. REQUIRES: sessionId from list_engineering_sessions.',
|
|
307
|
-
{
|
|
308
|
-
sessionId: z.string().describe("The session ID to get details for."),
|
|
309
|
-
includeSystemMessages: z.boolean().optional().describe("Include system messages (default false)."),
|
|
310
|
-
source: z.enum(["messages", "hooks", "all"]).optional().describe(
|
|
311
|
-
'Filter by source: "messages" (scanner messages only), "hooks" (hook events only), "all" (both interleaved). Default "all".'
|
|
312
|
-
),
|
|
313
|
-
eventType: z.string().optional().describe(
|
|
314
|
-
'Filter hook events by type (e.g. "PermissionRequest", "PreToolUse", "Stop"). Only applies when source includes hooks.'
|
|
315
|
-
),
|
|
316
|
-
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
317
|
-
offset: z.number().optional().describe("Skip first N entries for pagination.")
|
|
318
|
-
},
|
|
319
|
-
async (args) => toolHandler("get-session-timeline", args)
|
|
330
|
+
async (args) => handleTool("list-engineering-sessions", args)
|
|
320
331
|
);
|
|
332
|
+
registerSessionTimelineTool();
|
|
321
333
|
server2.tool(
|
|
322
334
|
"get_session_turns",
|
|
323
335
|
"Get per-turn token accounting for a coding agent session. USE FOR: understanding cost per turn, model used at each step, cache hit analysis. REQUIRES: sessionId from list_engineering_sessions.",
|
|
@@ -326,7 +338,7 @@ function registerTools(server2) {
|
|
|
326
338
|
limit: z.number().optional().describe("Max turns to return (default 50, max 200)."),
|
|
327
339
|
offset: z.number().optional().describe("Skip first N turns for pagination.")
|
|
328
340
|
},
|
|
329
|
-
async (args) =>
|
|
341
|
+
async (args) => handleTool("get-session-turns", args)
|
|
330
342
|
);
|
|
331
343
|
server2.tool(
|
|
332
344
|
"get_ai_spending",
|
|
@@ -335,10 +347,12 @@ function registerTools(server2) {
|
|
|
335
347
|
timeRange: z.enum(["1h", "6h", "24h", "7d", "30d"]).optional().describe("Default '7d'."),
|
|
336
348
|
since: z.string().optional().describe("ISO 8601 start timestamp."),
|
|
337
349
|
until: z.string().optional().describe("ISO 8601 end timestamp."),
|
|
338
|
-
groupBy: z.enum(["session", "model", "day"]).optional().describe("How to group results."),
|
|
339
|
-
target: z.enum(["claude", "codex", "gemini", "mastra"]).optional().describe("Filter by agent type. Omit for all.")
|
|
350
|
+
groupBy: z.enum(["session", "model", "day", "user"]).optional().describe("How to group results."),
|
|
351
|
+
target: z.enum(["claude", "codex", "gemini", "mastra"]).optional().describe("Filter by agent type. Omit for all."),
|
|
352
|
+
limit: z.number().int().min(1).max(500).optional().describe("Maximum groups to return, default 100."),
|
|
353
|
+
offset: z.number().int().min(0).optional().describe("Groups to skip; use nextOffset from the previous page.")
|
|
340
354
|
},
|
|
341
|
-
async (args) =>
|
|
355
|
+
async (args) => handleTool("get-ai-spending", args)
|
|
342
356
|
);
|
|
343
357
|
server2.tool(
|
|
344
358
|
"search_engineering_sessions",
|
|
@@ -351,7 +365,7 @@ function registerTools(server2) {
|
|
|
351
365
|
target: z.enum(["claude", "codex", "gemini", "mastra"]).optional().describe("Filter by agent type. Omit for all."),
|
|
352
366
|
limit: z.number().optional().describe("Max results (default 20).")
|
|
353
367
|
},
|
|
354
|
-
async (args) =>
|
|
368
|
+
async (args) => handleTool("search-engineering-sessions", args)
|
|
355
369
|
);
|
|
356
370
|
server2.tool(
|
|
357
371
|
"list_user_configs",
|
|
@@ -359,9 +373,9 @@ function registerTools(server2) {
|
|
|
359
373
|
{},
|
|
360
374
|
async () => {
|
|
361
375
|
try {
|
|
362
|
-
const orgSlug = getSelectedOrg();
|
|
376
|
+
const orgSlug = getSelectedOrg(environment.name);
|
|
363
377
|
if (!orgSlug) return errorResult("No org selected. Run `fml login`.");
|
|
364
|
-
const api = await getAuthenticatedClient();
|
|
378
|
+
const api = await getAuthenticatedClient(environment);
|
|
365
379
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
366
380
|
const result = await api.listUserConfigSnapshots(orgSlug);
|
|
367
381
|
return textResult(result);
|
|
@@ -381,9 +395,9 @@ function registerTools(server2) {
|
|
|
381
395
|
},
|
|
382
396
|
async (args) => {
|
|
383
397
|
try {
|
|
384
|
-
const orgSlug = getSelectedOrg();
|
|
398
|
+
const orgSlug = getSelectedOrg(environment.name);
|
|
385
399
|
if (!orgSlug) return errorResult("No org selected. Run `fml login`.");
|
|
386
|
-
const api = await getAuthenticatedClient();
|
|
400
|
+
const api = await getAuthenticatedClient(environment);
|
|
387
401
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
388
402
|
const result = await api.getUserConfigDetail(
|
|
389
403
|
orgSlug,
|
|
@@ -408,9 +422,9 @@ function registerTools(server2) {
|
|
|
408
422
|
},
|
|
409
423
|
async (args) => {
|
|
410
424
|
try {
|
|
411
|
-
const orgSlug = getSelectedOrg();
|
|
425
|
+
const orgSlug = getSelectedOrg(environment.name);
|
|
412
426
|
if (!orgSlug) return errorResult("No org selected. Run `fml login`.");
|
|
413
|
-
const api = await getAuthenticatedClient();
|
|
427
|
+
const api = await getAuthenticatedClient(environment);
|
|
414
428
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
415
429
|
const result = await api.listRepoConfigSnapshots(
|
|
416
430
|
orgSlug,
|
|
@@ -433,9 +447,9 @@ function registerTools(server2) {
|
|
|
433
447
|
},
|
|
434
448
|
async (args) => {
|
|
435
449
|
try {
|
|
436
|
-
const orgSlug = getSelectedOrg();
|
|
450
|
+
const orgSlug = getSelectedOrg(environment.name);
|
|
437
451
|
if (!orgSlug) return errorResult("No org selected. Run `fml login`.");
|
|
438
|
-
const api = await getAuthenticatedClient();
|
|
452
|
+
const api = await getAuthenticatedClient(environment);
|
|
439
453
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
440
454
|
const result = await api.getRepoConfigDetail(orgSlug, args.repository);
|
|
441
455
|
if (!result)
|
|
@@ -458,6 +472,16 @@ function registerTools(server2) {
|
|
|
458
472
|
}
|
|
459
473
|
};
|
|
460
474
|
const anamnesisResult = (result) => result.ok ? textResult(result.result) : errorResult(result.error ?? "Unknown error");
|
|
475
|
+
const withAnamnesisErrors = async (handler) => {
|
|
476
|
+
try {
|
|
477
|
+
return await handler();
|
|
478
|
+
} catch (err) {
|
|
479
|
+
Sentry.captureException(err);
|
|
480
|
+
return errorResult(
|
|
481
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
461
485
|
server2.tool(
|
|
462
486
|
"fml_path_history",
|
|
463
487
|
"Ground-truth history for a file path: PRs that touched it and review comments on it (newest first). USE FOR: understanding why a file looks the way it does, who changed it, related PRs.",
|
|
@@ -466,13 +490,13 @@ function registerTools(server2) {
|
|
|
466
490
|
repo: z.string().optional().describe("owner/repo. Defaults to the current working repo."),
|
|
467
491
|
limit: z.number().optional().describe("Max items per list (default 25).")
|
|
468
492
|
},
|
|
469
|
-
async (args) => {
|
|
493
|
+
async (args) => await withAnamnesisErrors(async () => {
|
|
470
494
|
const repo = resolveRepoArg(args.repo);
|
|
471
495
|
if (!repo)
|
|
472
496
|
return errorResult(
|
|
473
497
|
"Could not determine repo \u2014 pass `repo` (owner/repo)."
|
|
474
498
|
);
|
|
475
|
-
const api = await getAuthenticatedClient();
|
|
499
|
+
const api = await getAuthenticatedClient(environment);
|
|
476
500
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
477
501
|
return anamnesisResult(
|
|
478
502
|
await api.anamnesisContext("path", {
|
|
@@ -481,7 +505,7 @@ function registerTools(server2) {
|
|
|
481
505
|
limit: args.limit
|
|
482
506
|
})
|
|
483
507
|
);
|
|
484
|
-
}
|
|
508
|
+
})
|
|
485
509
|
);
|
|
486
510
|
server2.tool(
|
|
487
511
|
"fml_commit_context",
|
|
@@ -490,18 +514,18 @@ function registerTools(server2) {
|
|
|
490
514
|
sha: z.string().describe("Commit sha (full or abbreviated)."),
|
|
491
515
|
repo: z.string().optional().describe("owner/repo. Defaults to the current working repo.")
|
|
492
516
|
},
|
|
493
|
-
async (args) => {
|
|
517
|
+
async (args) => await withAnamnesisErrors(async () => {
|
|
494
518
|
const repo = resolveRepoArg(args.repo);
|
|
495
519
|
if (!repo)
|
|
496
520
|
return errorResult(
|
|
497
521
|
"Could not determine repo \u2014 pass `repo` (owner/repo)."
|
|
498
522
|
);
|
|
499
|
-
const api = await getAuthenticatedClient();
|
|
523
|
+
const api = await getAuthenticatedClient(environment);
|
|
500
524
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
501
525
|
return anamnesisResult(
|
|
502
526
|
await api.anamnesisContext("commit", { repo, sha: args.sha })
|
|
503
527
|
);
|
|
504
|
-
}
|
|
528
|
+
})
|
|
505
529
|
);
|
|
506
530
|
server2.tool(
|
|
507
531
|
"fml_pr_context",
|
|
@@ -511,13 +535,13 @@ function registerTools(server2) {
|
|
|
511
535
|
repo: z.string().optional().describe("owner/repo. Defaults to the current working repo."),
|
|
512
536
|
limit: z.number().optional().describe("Max items per list (default 25).")
|
|
513
537
|
},
|
|
514
|
-
async (args) => {
|
|
538
|
+
async (args) => await withAnamnesisErrors(async () => {
|
|
515
539
|
const repo = resolveRepoArg(args.repo);
|
|
516
540
|
if (!repo)
|
|
517
541
|
return errorResult(
|
|
518
542
|
"Could not determine repo \u2014 pass `repo` (owner/repo)."
|
|
519
543
|
);
|
|
520
|
-
const api = await getAuthenticatedClient();
|
|
544
|
+
const api = await getAuthenticatedClient(environment);
|
|
521
545
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
522
546
|
return anamnesisResult(
|
|
523
547
|
await api.anamnesisContext("pr", {
|
|
@@ -526,7 +550,7 @@ function registerTools(server2) {
|
|
|
526
550
|
limit: args.limit
|
|
527
551
|
})
|
|
528
552
|
);
|
|
529
|
-
}
|
|
553
|
+
})
|
|
530
554
|
);
|
|
531
555
|
server2.tool(
|
|
532
556
|
"fml_anamnesis_query",
|
|
@@ -540,16 +564,16 @@ function registerTools(server2) {
|
|
|
540
564
|
repo: z.string().optional().describe("owner/repo. Defaults to the current working repo."),
|
|
541
565
|
limit: z.number().optional().describe("Max facts (default 25).")
|
|
542
566
|
},
|
|
543
|
-
async (args) => {
|
|
567
|
+
async (args) => await withAnamnesisErrors(async () => {
|
|
544
568
|
const repo = resolveRepoArg(args.repo);
|
|
545
569
|
if (!repo)
|
|
546
570
|
return errorResult(
|
|
547
571
|
"Could not determine repo \u2014 pass `repo` (owner/repo)."
|
|
548
572
|
);
|
|
549
|
-
const api = await getAuthenticatedClient();
|
|
573
|
+
const api = await getAuthenticatedClient(environment);
|
|
550
574
|
if (!api) return errorResult("Not authenticated. Run `fml login`.");
|
|
551
575
|
return anamnesisResult(await api.anamnesisQuery({ ...args, repo }));
|
|
552
|
-
}
|
|
576
|
+
})
|
|
553
577
|
);
|
|
554
578
|
}
|
|
555
579
|
|
|
@@ -559,9 +583,10 @@ var server = new McpServer({
|
|
|
559
583
|
name: "fml",
|
|
560
584
|
version: "0.1.0"
|
|
561
585
|
});
|
|
562
|
-
registerTools(server);
|
|
563
586
|
async function main() {
|
|
564
587
|
await initSentry();
|
|
588
|
+
const environment = getActiveEnv();
|
|
589
|
+
registerTools(server, environment);
|
|
565
590
|
if (!fs.existsSync(FML_LOG_DIR)) {
|
|
566
591
|
fs.mkdirSync(FML_LOG_DIR, { recursive: true });
|
|
567
592
|
}
|
|
@@ -570,10 +595,10 @@ async function main() {
|
|
|
570
595
|
process.stderr.write = logStream.write.bind(
|
|
571
596
|
logStream
|
|
572
597
|
);
|
|
573
|
-
const tokens = readTokens();
|
|
574
|
-
const tokenValid = tokens ? await getValidToken() : null;
|
|
598
|
+
const tokens = readTokens(environment.name);
|
|
599
|
+
const tokenValid = tokens ? await getValidToken({ env: environment.name }) : null;
|
|
575
600
|
if (!tokens) {
|
|
576
|
-
const storePath =
|
|
601
|
+
const storePath = authStorePathFor(environment.name);
|
|
577
602
|
try {
|
|
578
603
|
const files = fs.readdirSync(FML_DATA_DIR).join(", ") || "(empty)";
|
|
579
604
|
console.error(
|
|
@@ -596,20 +621,9 @@ async function main() {
|
|
|
596
621
|
}
|
|
597
622
|
const transport = new StdioServerTransport();
|
|
598
623
|
await server.connect(transport);
|
|
599
|
-
if (!parsePanopticonRunning()) {
|
|
600
|
-
try {
|
|
601
|
-
const { handlePanopticonStart } = await import("../daemon-MSLDHLMH.js");
|
|
602
|
-
await handlePanopticonStart();
|
|
603
|
-
} catch {
|
|
604
|
-
console.error(
|
|
605
|
-
"[fml] Warning: could not start panopticon. Run `fml start` manually"
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
624
|
}
|
|
610
625
|
main().catch((err) => {
|
|
611
626
|
Sentry.captureException(err);
|
|
612
627
|
console.error("FML MCP server error:", err);
|
|
613
628
|
process.exit(1);
|
|
614
629
|
});
|
|
615
|
-
//# sourceMappingURL=server.js.map
|