@flowrelay/mcp-server 0.9.0 → 1.0.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 +3 -2
- package/dist/api.d.ts +32 -1
- package/dist/api.js +6 -2
- package/dist/index.js +49 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ It connects to Flow Relay API v1 using an API key and supports:
|
|
|
9
9
|
## Package
|
|
10
10
|
|
|
11
11
|
- Name: @flowrelay/mcp-server
|
|
12
|
-
- Version: 0.
|
|
12
|
+
- Version: 1.0.0
|
|
13
13
|
|
|
14
14
|
## What Is Included
|
|
15
15
|
|
|
@@ -18,6 +18,7 @@ The server currently exposes these tools:
|
|
|
18
18
|
- get_workspace_context
|
|
19
19
|
- list_projects
|
|
20
20
|
- set_active_project
|
|
21
|
+
- list_filter_options (lists the selectable resources, branches, event types and priorities per source for a project – call this before generating so handoff/insight filters use real values instead of guesses)
|
|
21
22
|
- list_handoffs (returns project-specific handoffs, or an aggregated view of all accessible project handoffs if no project is active; filter by status: active, archived, or all)
|
|
22
23
|
- generate_handoff (processed asynchronously, and the server automatically polls until the job finishes. Requires active project or project_id. Accepts per-source filters: projects, eventTypes, branches, priorities.)
|
|
23
24
|
- generate_correlation_insight (accepts per-source filters)
|
|
@@ -28,7 +29,7 @@ The server currently exposes these tools:
|
|
|
28
29
|
- list_events
|
|
29
30
|
- list_untracked_resources (lists event-producing resources not scoped to any project – useful for discovering untracked activity)
|
|
30
31
|
- discord_list_channels
|
|
31
|
-
- discord_send_message
|
|
32
|
+
- discord_send_message (plain text, or a handoff/insight by id or `last_*` rendered to Markdown and attached as a `.md` file)
|
|
32
33
|
|
|
33
34
|
## Environment Variables
|
|
34
35
|
|
package/dist/api.d.ts
CHANGED
|
@@ -98,6 +98,30 @@ export type GenerateInsightResponse = {
|
|
|
98
98
|
jobId: string;
|
|
99
99
|
status: AiJobStatus;
|
|
100
100
|
};
|
|
101
|
+
export interface AvailableSourceFilter {
|
|
102
|
+
projects: {
|
|
103
|
+
id: string;
|
|
104
|
+
label: string;
|
|
105
|
+
}[];
|
|
106
|
+
eventTypes: {
|
|
107
|
+
value: string;
|
|
108
|
+
label: string;
|
|
109
|
+
}[];
|
|
110
|
+
branches?: {
|
|
111
|
+
value: string;
|
|
112
|
+
label: string;
|
|
113
|
+
}[];
|
|
114
|
+
branchesByProject?: Record<string, {
|
|
115
|
+
value: string;
|
|
116
|
+
label: string;
|
|
117
|
+
}[]>;
|
|
118
|
+
defaultBranchByProject?: Record<string, string>;
|
|
119
|
+
priorities: {
|
|
120
|
+
value: string;
|
|
121
|
+
label: string;
|
|
122
|
+
}[];
|
|
123
|
+
}
|
|
124
|
+
export type AvailableFilters = Record<string, AvailableSourceFilter>;
|
|
101
125
|
export declare class FlowRelayAPI {
|
|
102
126
|
private baseUrl;
|
|
103
127
|
private apiKey;
|
|
@@ -105,6 +129,7 @@ export declare class FlowRelayAPI {
|
|
|
105
129
|
private requestRaw;
|
|
106
130
|
private request;
|
|
107
131
|
listProjects(): Promise<TenantContext>;
|
|
132
|
+
getHandoffFilters(projectId: string): Promise<AvailableFilters>;
|
|
108
133
|
listHandoffs(status?: string, limit?: number, projectId?: string | null): Promise<{
|
|
109
134
|
handoffs: HandoffResult[];
|
|
110
135
|
}>;
|
|
@@ -155,7 +180,13 @@ export declare class FlowRelayAPI {
|
|
|
155
180
|
topic: string;
|
|
156
181
|
}>;
|
|
157
182
|
}>;
|
|
158
|
-
discordSendMessage(channelId: string,
|
|
183
|
+
discordSendMessage(channelId: string, payload: {
|
|
184
|
+
content?: string;
|
|
185
|
+
handoff_id?: string;
|
|
186
|
+
insight_id?: string;
|
|
187
|
+
artifact?: 'last_handoff' | 'last_correlation' | 'last_onboarding' | 'last_architecture';
|
|
188
|
+
project_id?: string;
|
|
189
|
+
}): Promise<{
|
|
159
190
|
ok: boolean;
|
|
160
191
|
message_id: string;
|
|
161
192
|
}>;
|
package/dist/api.js
CHANGED
|
@@ -34,6 +34,10 @@ export class FlowRelayAPI {
|
|
|
34
34
|
async listProjects() {
|
|
35
35
|
return this.request('/projects');
|
|
36
36
|
}
|
|
37
|
+
async getHandoffFilters(projectId) {
|
|
38
|
+
const { filters } = await this.request(`/handoffs/filters?project_id=${encodeURIComponent(projectId)}`);
|
|
39
|
+
return filters ?? {};
|
|
40
|
+
}
|
|
37
41
|
async listHandoffs(status = 'active', limit = 10, projectId) {
|
|
38
42
|
const params = new URLSearchParams();
|
|
39
43
|
params.set('status', status);
|
|
@@ -105,10 +109,10 @@ export class FlowRelayAPI {
|
|
|
105
109
|
async discordListChannels() {
|
|
106
110
|
return this.request('/discord/channels');
|
|
107
111
|
}
|
|
108
|
-
async discordSendMessage(channelId,
|
|
112
|
+
async discordSendMessage(channelId, payload) {
|
|
109
113
|
return this.request('/discord/send', {
|
|
110
114
|
method: 'POST',
|
|
111
|
-
body: JSON.stringify({ channel_id: channelId,
|
|
115
|
+
body: JSON.stringify({ channel_id: channelId, ...payload }),
|
|
112
116
|
});
|
|
113
117
|
}
|
|
114
118
|
async listUntrackedResources() {
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const api = new FlowRelayAPI(apiKey, process.env.FLOWRELAY_BASE_URL);
|
|
|
59
59
|
let activeProjectId = normalizeProjectId(process.env.FLOWRELAY_PROJECT_ID);
|
|
60
60
|
const server = new McpServer({
|
|
61
61
|
name: 'flowrelay',
|
|
62
|
-
version: '0.
|
|
62
|
+
version: '1.0.0',
|
|
63
63
|
});
|
|
64
64
|
async function getTenantContext() {
|
|
65
65
|
const context = await api.listProjects();
|
|
@@ -326,12 +326,22 @@ server.tool('discord_list_channels', 'List text channels in your connected Disco
|
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
// ── Tool: discord send message ──────────────────────────────────────
|
|
329
|
-
server.tool('discord_send_message', 'Send
|
|
330
|
-
channel_id: z.string().describe('The Discord channel ID to send
|
|
331
|
-
content: z.string().describe('
|
|
332
|
-
|
|
329
|
+
server.tool('discord_send_message', 'Send to a Discord channel in your connected server. Provide exactly one of: content (inline text); handoff_id or insight_id (sends that artifact, rendered to Markdown, as a .md file attachment); or artifact (last_handoff / last_correlation / last_onboarding / last_architecture, with project_id) to send the latest active artifact of that kind.', {
|
|
330
|
+
channel_id: z.string().describe('The Discord channel ID to send to'),
|
|
331
|
+
content: z.string().optional().describe('Inline message text. Mutually exclusive with handoff_id / insight_id / artifact'),
|
|
332
|
+
handoff_id: z.string().optional().describe('UUID of a handoff to render and attach as a .md file'),
|
|
333
|
+
insight_id: z.string().optional().describe('UUID of an insight to render and attach as a .md file'),
|
|
334
|
+
artifact: z
|
|
335
|
+
.enum(['last_handoff', 'last_correlation', 'last_onboarding', 'last_architecture'])
|
|
336
|
+
.optional()
|
|
337
|
+
.describe('Send the latest active artifact of this kind. Requires project_id'),
|
|
338
|
+
project_id: z.string().optional().describe('Project UUID. Required only when artifact is set'),
|
|
339
|
+
}, async ({ channel_id, content, handoff_id, insight_id, artifact, project_id, }) => {
|
|
340
|
+
if (!content && !handoff_id && !insight_id && !artifact) {
|
|
341
|
+
return { content: [{ type: 'text', text: 'Provide content, handoff_id, insight_id, or artifact.' }] };
|
|
342
|
+
}
|
|
333
343
|
try {
|
|
334
|
-
const result = await api.discordSendMessage(channel_id, content);
|
|
344
|
+
const result = await api.discordSendMessage(channel_id, { content, handoff_id, insight_id, artifact, project_id });
|
|
335
345
|
return { content: [{ type: 'text', text: `Message sent (ID: ${result.message_id})` }] };
|
|
336
346
|
}
|
|
337
347
|
catch (err) {
|
|
@@ -446,6 +456,39 @@ server.tool('list_insights', 'List project AI insights for a selected project sc
|
|
|
446
456
|
return { content: [{ type: 'text', text: `Failed to list insights: ${err.message}` }] };
|
|
447
457
|
}
|
|
448
458
|
});
|
|
459
|
+
// ── Tool: list filter options ───────────────────────────────────────
|
|
460
|
+
server.tool('list_filter_options', 'List the real per-source filter values (resources, branches, event types, priorities) available for a project. Call this BEFORE generate_handoff or any generate_*_insight so the "filters" argument uses real values instead of guesses. Pass a resource id into filters[source].projects, a branch name into .branches, an event type into .eventTypes, a priority into .priorities.', {
|
|
461
|
+
project_id: z.string().optional().describe('Project scope. Omit to use the active project.'),
|
|
462
|
+
}, async ({ project_id }) => {
|
|
463
|
+
try {
|
|
464
|
+
const resolved = await requireProject(project_id);
|
|
465
|
+
const filters = await api.getHandoffFilters(resolved.projectId);
|
|
466
|
+
const sources = Object.keys(filters);
|
|
467
|
+
if (sources.length === 0) {
|
|
468
|
+
return { content: [{ type: 'text', text: `No connected sources with filter options for ${resolved.project.name}.` }] };
|
|
469
|
+
}
|
|
470
|
+
const blocks = sources.map((src) => {
|
|
471
|
+
const f = filters[src];
|
|
472
|
+
const lines = [`### ${src}`];
|
|
473
|
+
if (f.projects?.length) {
|
|
474
|
+
const items = f.projects.slice(0, 50).map((p) => (p.label && p.label !== p.id ? `${p.label} (id: ${p.id})` : p.id));
|
|
475
|
+
const more = f.projects.length > 50 ? ` (+${f.projects.length - 50} more)` : '';
|
|
476
|
+
lines.push(`- Resources: ${items.join(', ')}${more}`);
|
|
477
|
+
}
|
|
478
|
+
if (f.branches?.length)
|
|
479
|
+
lines.push(`- Branches: ${f.branches.map((b) => b.value).join(', ')}`);
|
|
480
|
+
if (f.eventTypes?.length)
|
|
481
|
+
lines.push(`- Event types: ${f.eventTypes.map((e) => e.value).join(', ')}`);
|
|
482
|
+
if (f.priorities?.length)
|
|
483
|
+
lines.push(`- Priorities: ${f.priorities.map((p) => p.value).join(', ')}`);
|
|
484
|
+
return lines.join('\n');
|
|
485
|
+
});
|
|
486
|
+
return { content: [{ type: 'text', text: `Filter options for ${resolved.project.name}:\n\n${blocks.join('\n\n')}` }] };
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
return { content: [{ type: 'text', text: `Could not load filter options: ${err.message}` }] };
|
|
490
|
+
}
|
|
491
|
+
});
|
|
449
492
|
// ── Start ────────────────────────────────────────────────────────────
|
|
450
493
|
const transport = new StdioServerTransport();
|
|
451
494
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowrelay/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Flow Relay MCP Server for Claude Desktop and Claude Code – handoffs, integrations, and context events via natural conversation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|