@flowrelay/mcp-server 1.0.9 → 1.0.11
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 +4 -3
- package/dist/api.d.ts +17 -1
- package/dist/api.js +11 -0
- package/dist/index.js +54 -2
- 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: 1.0.
|
|
12
|
+
- Version: 1.0.10
|
|
13
13
|
|
|
14
14
|
## What Is Included
|
|
15
15
|
|
|
@@ -20,12 +20,13 @@ The server currently exposes these tools:
|
|
|
20
20
|
- set_active_project
|
|
21
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; also reports whether Figma is selectable, since Figma requires the project's processing region to be Global)
|
|
22
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)
|
|
23
|
+
- ask_project (ask a question about a project and get an answer grounded in codebase, baselines and 14 days of activity; 2 credits per question)
|
|
23
24
|
- 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.)
|
|
24
25
|
- generate_correlation_insight (accepts per-source filters)
|
|
25
26
|
- generate_onboarding_brief (accepts per-source filters)
|
|
26
27
|
- generate_architecture_insight (accepts per-source filters)
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
- generate_release_notes (generates release notes or PR descriptions from code activity; accepts per-source filters)
|
|
29
|
+
- list_digests (lists past scheduled activity digests for a project)
|
|
29
30
|
- list_insights
|
|
30
31
|
- list_integrations
|
|
31
32
|
- list_events
|
package/dist/api.d.ts
CHANGED
|
@@ -162,7 +162,23 @@ export declare class FlowRelayAPI {
|
|
|
162
162
|
listInsights(projectId: string, kind?: string, status?: string, limit?: number): Promise<{
|
|
163
163
|
insights: InsightResult[];
|
|
164
164
|
}>;
|
|
165
|
-
|
|
165
|
+
askProject(projectId: string, question: string, filters?: Record<string, unknown>): Promise<{
|
|
166
|
+
answer: string;
|
|
167
|
+
citations: string[];
|
|
168
|
+
}>;
|
|
169
|
+
generateInsight(projectId: string, kind: 'correlation' | 'onboarding' | 'architecture' | 'release_notes', body?: Record<string, unknown>): Promise<GenerateInsightResponse>;
|
|
170
|
+
listDigests(projectId: string, limit?: number): Promise<{
|
|
171
|
+
digests: Array<{
|
|
172
|
+
id: string;
|
|
173
|
+
projectId: string;
|
|
174
|
+
generatedBy: string;
|
|
175
|
+
periodStart: string;
|
|
176
|
+
periodEnd: string;
|
|
177
|
+
content: Record<string, unknown>;
|
|
178
|
+
markdown: string;
|
|
179
|
+
createdAt: string;
|
|
180
|
+
}>;
|
|
181
|
+
}>;
|
|
166
182
|
listIntegrations(projectId?: string | null): Promise<{
|
|
167
183
|
integrations: Array<{
|
|
168
184
|
source: string;
|
package/dist/api.js
CHANGED
|
@@ -84,12 +84,23 @@ export class FlowRelayAPI {
|
|
|
84
84
|
params.set('limit', String(limit));
|
|
85
85
|
return this.request(`/projects/${projectId}/insights?${params.toString()}`);
|
|
86
86
|
}
|
|
87
|
+
async askProject(projectId, question, filters) {
|
|
88
|
+
return this.request(`/projects/${projectId}/qa`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: JSON.stringify(filters ? { question, filters } : { question }),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
87
93
|
async generateInsight(projectId, kind, body) {
|
|
88
94
|
return this.request(`/projects/${projectId}/insights/${kind}`, {
|
|
89
95
|
method: 'POST',
|
|
90
96
|
body: JSON.stringify(body ?? {}),
|
|
91
97
|
});
|
|
92
98
|
}
|
|
99
|
+
async listDigests(projectId, limit = 10) {
|
|
100
|
+
const params = new URLSearchParams();
|
|
101
|
+
params.set('limit', String(limit));
|
|
102
|
+
return this.request(`/projects/${projectId}/digests?${params.toString()}`);
|
|
103
|
+
}
|
|
93
104
|
async listIntegrations(projectId) {
|
|
94
105
|
const params = new URLSearchParams();
|
|
95
106
|
if (projectId)
|
package/dist/index.js
CHANGED
|
@@ -30,13 +30,14 @@ const SOURCES = [
|
|
|
30
30
|
'buildkite',
|
|
31
31
|
'circleci',
|
|
32
32
|
'vercel',
|
|
33
|
+
'incident_io',
|
|
33
34
|
];
|
|
34
35
|
const SourceEnum = z.enum(SOURCES);
|
|
35
36
|
const SourceFilterSchema = z.object({
|
|
36
37
|
projects: z.array(z.string()).optional().describe('Resource ids from list_filter_options[source].projects (repos, channels, boards). Use the exact id, not the display label.'),
|
|
37
38
|
eventTypes: z.array(z.string()).optional().describe('Event-type values from list_filter_options (e.g. "push", "issue_created"). Case-sensitive; provider-driven. A value that does not exist matches no events (it is not rejected).'),
|
|
38
39
|
branches: z.array(z.string()).optional().describe('Git branch names (e.g. "main", "develop") – git sources only (github, gitlab, bitbucket, azure_devops). Using this on a non-git source is a 400.'),
|
|
39
|
-
priorities: z.array(z.string()).optional().describe('Priority values from list_filter_options (e.g. "high", "urgent") – jira, linear, sentry, pagerduty only. Using this on another source is a 400; unrecognized values simply match no events.'),
|
|
40
|
+
priorities: z.array(z.string()).optional().describe('Priority values from list_filter_options (e.g. "high", "urgent") – jira, linear, sentry, pagerduty, incident_io only. Using this on another source is a 400; unrecognized values simply match no events.'),
|
|
40
41
|
});
|
|
41
42
|
const FiltersSchema = z.record(z.string(), SourceFilterSchema)
|
|
42
43
|
.optional()
|
|
@@ -459,9 +460,45 @@ server.tool('generate_architecture_insight', 'Generate an architecture insight:
|
|
|
459
460
|
}
|
|
460
461
|
});
|
|
461
462
|
// ── Tool: list insights ─────────────────────────────────────────────
|
|
463
|
+
server.tool('generate_release_notes', 'Generate release notes or a PR description from recent code activity (commits, PRs, builds). Runs synchronously and returns Markdown. Consumes 3 credits on success.', {
|
|
464
|
+
project_id: z.string().describe('Project id from list_projects.'),
|
|
465
|
+
source: SourceEnum.default('github').describe('Code source id (github, gitlab, bitbucket, azure_devops).'),
|
|
466
|
+
repo: z.string().optional().describe('Repository name to scope changes to.'),
|
|
467
|
+
style: z.enum(['release_notes', 'pr_description']).default('release_notes').describe('Output style.'),
|
|
468
|
+
}, async ({ project_id, source, repo, style }) => {
|
|
469
|
+
try {
|
|
470
|
+
const res = await api.generateInsight(project_id, 'release_notes', { source, repo, style });
|
|
471
|
+
const { job, result } = await api.waitForJob(res.jobId);
|
|
472
|
+
if (job.status === 'failed' || !result) {
|
|
473
|
+
const reason = job.error ?? 'unknown error';
|
|
474
|
+
return { content: [{ type: 'text', text: `Could not generate release notes: ${reason}` }] };
|
|
475
|
+
}
|
|
476
|
+
const insight = result;
|
|
477
|
+
return { content: [{ type: 'text', text: insight.markdown ?? `# ${insight.title}\n\n${insight.summary}` }] };
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
return { content: [{ type: 'text', text: `Failed: ${err.message}` }] };
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
server.tool('list_digests', 'List past scheduled activity digests for a project.', {
|
|
484
|
+
project_id: z.string().describe('Project id from list_projects.'),
|
|
485
|
+
limit: z.number().int().min(1).max(50).default(10).describe('Max digests to return (1-50, default 10).'),
|
|
486
|
+
}, async ({ project_id, limit }) => {
|
|
487
|
+
try {
|
|
488
|
+
const { digests } = await api.listDigests(project_id, limit);
|
|
489
|
+
if (digests.length === 0) {
|
|
490
|
+
return { content: [{ type: 'text', text: 'No digests found for this project.' }] };
|
|
491
|
+
}
|
|
492
|
+
const text = digests.map((d) => d.markdown).join('\n\n---\n\n');
|
|
493
|
+
return { content: [{ type: 'text', text }] };
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
return { content: [{ type: 'text', text: `Failed to list digests: ${err.message}` }] };
|
|
497
|
+
}
|
|
498
|
+
});
|
|
462
499
|
server.tool('list_insights', 'List existing insights for a project (newest first) with their content. Use this to read what has already been generated before spending credits on a new generate_*_insight. Each row\'s id can be sent to Discord via discord_send_message.', {
|
|
463
500
|
project_id: z.string().describe('Project id from list_projects to list insights for.'),
|
|
464
|
-
kind: z.enum(['onboarding_brief', 'cross_source_correlation', 'architecture_insight']).optional().describe('Restrict to one kind
|
|
501
|
+
kind: z.enum(['onboarding_brief', 'cross_source_correlation', 'architecture_insight', 'release_notes']).optional().describe('Restrict to one kind. Omit for all kinds.'),
|
|
465
502
|
status: z.enum(['active', 'archived', 'all']).default('active').describe('active = current, archived = superseded, all = both.'),
|
|
466
503
|
limit: z.number().int().min(1).max(50).default(20).describe('Max insights to return (1-50, default 20).'),
|
|
467
504
|
}, async ({ project_id, kind, status, limit }) => {
|
|
@@ -479,6 +516,21 @@ server.tool('list_insights', 'List existing insights for a project (newest first
|
|
|
479
516
|
return { content: [{ type: 'text', text: `Failed to list insights: ${err.message}` }] };
|
|
480
517
|
}
|
|
481
518
|
});
|
|
519
|
+
// ── Tool: ask project ───────────────────────────────────────────────
|
|
520
|
+
server.tool('ask_project', 'Ask one question about a project and get an answer grounded in its indexed codebase, connected baselines and last 14 days of activity. Answers synchronously – there is no job to poll. Costs 2 credits per question, so prefer list_handoffs / list_insights when an existing artifact already answers it.', {
|
|
521
|
+
question: z.string().min(1).max(2000).describe('The question, 1-2000 characters.'),
|
|
522
|
+
project_id: z.string().optional().describe('Project scope. Omit to use the active project.'),
|
|
523
|
+
}, async ({ question, project_id }) => {
|
|
524
|
+
try {
|
|
525
|
+
const resolved = await requireProject(project_id);
|
|
526
|
+
const { answer, citations } = await api.askProject(resolved.projectId, question);
|
|
527
|
+
const refs = citations.length > 0 ? `\n\n_Evidence: ${citations.join(' ')}_` : '';
|
|
528
|
+
return { content: [{ type: 'text', text: `${answer}${refs}` }] };
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
return { content: [{ type: 'text', text: `Could not answer: ${err.message}` }] };
|
|
532
|
+
}
|
|
533
|
+
});
|
|
482
534
|
// ── Tool: list filter options ───────────────────────────────────────
|
|
483
535
|
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.', {
|
|
484
536
|
project_id: z.string().optional().describe('Project scope. Omit to use the active project.'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowrelay/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
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",
|