@flowrelay/mcp-server 0.6.0 → 0.9.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 +104 -105
- package/dist/api.d.ts +14 -6
- package/dist/api.js +5 -6
- package/dist/index.js +85 -34
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,105 +1,104 @@
|
|
|
1
|
-
# Flow Relay MCP Server
|
|
2
|
-
|
|
3
|
-
Flow Relay MCP Server adds project-aware, multi-tenant Flow Relay tools to MCP clients such as Claude Desktop and Claude Code.
|
|
4
|
-
|
|
5
|
-
It connects to Flow Relay API v1 using an API key and supports
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- discord_list_channels
|
|
31
|
-
- discord_send_message
|
|
32
|
-
|
|
33
|
-
## Environment Variables
|
|
34
|
-
|
|
35
|
-
Required:
|
|
36
|
-
|
|
37
|
-
- FLOWRELAY_API_KEY
|
|
38
|
-
|
|
39
|
-
Optional:
|
|
40
|
-
|
|
41
|
-
- FLOWRELAY_PROJECT_ID
|
|
42
|
-
- FLOWRELAY_BASE_URL
|
|
43
|
-
|
|
44
|
-
If FLOWRELAY_PROJECT_ID is set, it becomes the default project context for project-aware tools unless you override it per call.
|
|
45
|
-
|
|
46
|
-
## Quick Start (Claude Desktop)
|
|
47
|
-
|
|
48
|
-
Add this to your claude_desktop_config.json:
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"mcpServers": {
|
|
53
|
-
"flowrelay": {
|
|
54
|
-
"command": "npx",
|
|
55
|
-
"args": ["-y", "@flowrelay/mcp-server"],
|
|
56
|
-
"env": {
|
|
57
|
-
"FLOWRELAY_API_KEY": "fr_your_api_key_here",
|
|
58
|
-
"FLOWRELAY_PROJECT_ID": "
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Multi-Tenant Behavior
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
- You can select a project during the MCP session with set_active_project.
|
|
69
|
-
- You can override scope per call by passing project_id where supported.
|
|
70
|
-
|
|
71
|
-
Recommended flow:
|
|
72
|
-
|
|
73
|
-
1. Call get_workspace_context
|
|
74
|
-
2. Call list_projects
|
|
75
|
-
3. Call set_active_project
|
|
76
|
-
4. Run handoff and query tools in the selected scope
|
|
77
|
-
|
|
78
|
-
## Local Development
|
|
79
|
-
|
|
80
|
-
From this folder:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
npm install
|
|
84
|
-
npm run build
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Create a tarball package:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
npm pack
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Troubleshooting
|
|
94
|
-
|
|
95
|
-
- Error: Missing FLOWRELAY_API_KEY
|
|
96
|
-
- Set FLOWRELAY_API_KEY in your MCP client configuration.
|
|
97
|
-
- Project not found or inaccessible
|
|
98
|
-
- Run list_projects and use one of the returned IDs.
|
|
99
|
-
- No events or handoffs returned
|
|
100
|
-
- Verify active scope and data availability in that scope.
|
|
101
|
-
|
|
102
|
-
## Related Docs
|
|
103
|
-
|
|
104
|
-
- Repository overview: ../README.md
|
|
105
|
-
- Release notes: ../RELEASE_NOTES_v0.4.0.md
|
|
1
|
+
# Flow Relay MCP Server
|
|
2
|
+
|
|
3
|
+
Flow Relay MCP Server adds project-aware, multi-tenant Flow Relay tools to MCP clients such as Claude Desktop and Claude Code.
|
|
4
|
+
|
|
5
|
+
It connects to Flow Relay API v1 using an API key and supports:
|
|
6
|
+
|
|
7
|
+
- Project scope (personal project or organization project). Every handoff and AI insight is tied to a project. Events and integrations remain user-level.
|
|
8
|
+
|
|
9
|
+
## Package
|
|
10
|
+
|
|
11
|
+
- Name: @flowrelay/mcp-server
|
|
12
|
+
- Version: 0.9.0
|
|
13
|
+
|
|
14
|
+
## What Is Included
|
|
15
|
+
|
|
16
|
+
The server currently exposes these tools:
|
|
17
|
+
|
|
18
|
+
- get_workspace_context
|
|
19
|
+
- list_projects
|
|
20
|
+
- set_active_project
|
|
21
|
+
- 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
|
+
- 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
|
+
- generate_correlation_insight (accepts per-source filters)
|
|
24
|
+
- generate_onboarding_brief (accepts per-source filters)
|
|
25
|
+
- generate_architecture_insight (accepts per-source filters)
|
|
26
|
+
- list_insights
|
|
27
|
+
- list_integrations
|
|
28
|
+
- list_events
|
|
29
|
+
- list_untracked_resources (lists event-producing resources not scoped to any project – useful for discovering untracked activity)
|
|
30
|
+
- discord_list_channels
|
|
31
|
+
- discord_send_message
|
|
32
|
+
|
|
33
|
+
## Environment Variables
|
|
34
|
+
|
|
35
|
+
Required:
|
|
36
|
+
|
|
37
|
+
- FLOWRELAY_API_KEY
|
|
38
|
+
|
|
39
|
+
Optional:
|
|
40
|
+
|
|
41
|
+
- FLOWRELAY_PROJECT_ID
|
|
42
|
+
- FLOWRELAY_BASE_URL
|
|
43
|
+
|
|
44
|
+
If FLOWRELAY_PROJECT_ID is set, it becomes the default project context for project-aware tools unless you override it per call.
|
|
45
|
+
|
|
46
|
+
## Quick Start (Claude Desktop)
|
|
47
|
+
|
|
48
|
+
Add this to your claude_desktop_config.json:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"flowrelay": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["-y", "@flowrelay/mcp-server"],
|
|
56
|
+
"env": {
|
|
57
|
+
"FLOWRELAY_API_KEY": "fr_your_api_key_here",
|
|
58
|
+
"FLOWRELAY_PROJECT_ID": "your_project_id"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Multi-Tenant Behavior
|
|
66
|
+
|
|
67
|
+
- Every handoff and insight is tied to a project; you need an active project (`set_active_project`) or `project_id`. Events and integrations remain user-level.
|
|
68
|
+
- You can select a project during the MCP session with set_active_project.
|
|
69
|
+
- You can override scope per call by passing project_id where supported.
|
|
70
|
+
|
|
71
|
+
Recommended flow:
|
|
72
|
+
|
|
73
|
+
1. Call get_workspace_context
|
|
74
|
+
2. Call list_projects
|
|
75
|
+
3. Call set_active_project
|
|
76
|
+
4. Run handoff and query tools in the selected scope
|
|
77
|
+
|
|
78
|
+
## Local Development
|
|
79
|
+
|
|
80
|
+
From this folder:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install
|
|
84
|
+
npm run build
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Create a tarball package:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm pack
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Troubleshooting
|
|
94
|
+
|
|
95
|
+
- Error: Missing FLOWRELAY_API_KEY
|
|
96
|
+
- Set FLOWRELAY_API_KEY in your MCP client configuration.
|
|
97
|
+
- Project not found or inaccessible
|
|
98
|
+
- Run list_projects and use one of the returned IDs.
|
|
99
|
+
- No events or handoffs returned
|
|
100
|
+
- Verify active scope and data availability in that scope.
|
|
101
|
+
|
|
102
|
+
## Related Docs
|
|
103
|
+
|
|
104
|
+
- Repository overview: ../README.md
|
package/dist/api.d.ts
CHANGED
|
@@ -78,9 +78,19 @@ export interface InsightResult {
|
|
|
78
78
|
created_at: string;
|
|
79
79
|
updated_at?: string;
|
|
80
80
|
}
|
|
81
|
+
export interface UntrackedResource {
|
|
82
|
+
source: string;
|
|
83
|
+
resource_id: string;
|
|
84
|
+
resource_name: string;
|
|
85
|
+
resource_type: string;
|
|
86
|
+
}
|
|
87
|
+
export interface SourceFilter {
|
|
88
|
+
projects?: string[];
|
|
89
|
+
eventTypes?: string[];
|
|
90
|
+
branches?: string[];
|
|
91
|
+
priorities?: string[];
|
|
92
|
+
}
|
|
81
93
|
export type GenerateHandoffResponse = {
|
|
82
|
-
handoff: HandoffResult;
|
|
83
|
-
} | {
|
|
84
94
|
jobId: string;
|
|
85
95
|
status: AiJobStatus;
|
|
86
96
|
};
|
|
@@ -98,10 +108,7 @@ export declare class FlowRelayAPI {
|
|
|
98
108
|
listHandoffs(status?: string, limit?: number, projectId?: string | null): Promise<{
|
|
99
109
|
handoffs: HandoffResult[];
|
|
100
110
|
}>;
|
|
101
|
-
generateHandoff(sources
|
|
102
|
-
projects?: string[];
|
|
103
|
-
eventTypes?: string[];
|
|
104
|
-
}>, projectId?: string | null): Promise<GenerateHandoffResponse>;
|
|
111
|
+
generateHandoff(sources: string[] | undefined, filters: Record<string, SourceFilter> | undefined, projectId: string): Promise<GenerateHandoffResponse>;
|
|
105
112
|
getJob(jobId: string): Promise<{
|
|
106
113
|
job: AiJob;
|
|
107
114
|
result: HandoffResult | InsightResult | null;
|
|
@@ -152,4 +159,5 @@ export declare class FlowRelayAPI {
|
|
|
152
159
|
ok: boolean;
|
|
153
160
|
message_id: string;
|
|
154
161
|
}>;
|
|
162
|
+
listUntrackedResources(): Promise<UntrackedResource[]>;
|
|
155
163
|
}
|
package/dist/api.js
CHANGED
|
@@ -48,15 +48,11 @@ export class FlowRelayAPI {
|
|
|
48
48
|
body.sources = sources;
|
|
49
49
|
if (filters && Object.keys(filters).length > 0)
|
|
50
50
|
body.filters = filters;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { status, body: data } = await this.requestRaw('/handoffs', {
|
|
51
|
+
body.project_id = projectId;
|
|
52
|
+
return this.request('/handoffs', {
|
|
54
53
|
method: 'POST',
|
|
55
54
|
body: JSON.stringify(body),
|
|
56
55
|
});
|
|
57
|
-
// 202 → async job; 200 → inline handoff. Caller branches on the shape.
|
|
58
|
-
void status;
|
|
59
|
-
return data;
|
|
60
56
|
}
|
|
61
57
|
async getJob(jobId) {
|
|
62
58
|
return (await this.requestRaw(`/jobs/${jobId}`)).body;
|
|
@@ -115,4 +111,7 @@ export class FlowRelayAPI {
|
|
|
115
111
|
body: JSON.stringify({ channel_id: channelId, content }),
|
|
116
112
|
});
|
|
117
113
|
}
|
|
114
|
+
async listUntrackedResources() {
|
|
115
|
+
return this.request('/integrations/untracked');
|
|
116
|
+
}
|
|
118
117
|
}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,15 @@ const SOURCES = [
|
|
|
21
21
|
'datadog',
|
|
22
22
|
];
|
|
23
23
|
const SourceEnum = z.enum(SOURCES);
|
|
24
|
+
const SourceFilterSchema = z.object({
|
|
25
|
+
projects: z.array(z.string()).optional().describe('Filter to specific repos, channels, projects, or teams'),
|
|
26
|
+
eventTypes: z.array(z.string()).optional().describe('Filter to specific event types (e.g. "push", "issue_created")'),
|
|
27
|
+
branches: z.array(z.string()).optional().describe('Git branch names (e.g. "main", "develop") – versioning sources only'),
|
|
28
|
+
priorities: z.array(z.string()).optional().describe('Priority levels (e.g. "high", "urgent") – Jira, Linear, Sentry only'),
|
|
29
|
+
});
|
|
30
|
+
const FiltersSchema = z.record(z.string(), SourceFilterSchema)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Per-source advanced filters, AND-combined across dimensions');
|
|
24
33
|
function normalizeProjectId(value) {
|
|
25
34
|
if (typeof value !== 'string')
|
|
26
35
|
return null;
|
|
@@ -29,7 +38,7 @@ function normalizeProjectId(value) {
|
|
|
29
38
|
}
|
|
30
39
|
function formatProjectScope(project) {
|
|
31
40
|
if (!project)
|
|
32
|
-
return 'No project (
|
|
41
|
+
return 'No project selected (required for handoffs & insights)';
|
|
33
42
|
if (project.project_type === 'personal') {
|
|
34
43
|
return `Personal project: ${project.name}`;
|
|
35
44
|
}
|
|
@@ -50,7 +59,7 @@ const api = new FlowRelayAPI(apiKey, process.env.FLOWRELAY_BASE_URL);
|
|
|
50
59
|
let activeProjectId = normalizeProjectId(process.env.FLOWRELAY_PROJECT_ID);
|
|
51
60
|
const server = new McpServer({
|
|
52
61
|
name: 'flowrelay',
|
|
53
|
-
version: '0.
|
|
62
|
+
version: '0.9.0',
|
|
54
63
|
});
|
|
55
64
|
async function getTenantContext() {
|
|
56
65
|
const context = await api.listProjects();
|
|
@@ -73,6 +82,20 @@ async function resolveProject(projectId) {
|
|
|
73
82
|
}
|
|
74
83
|
return { projectId: resolvedProjectId, project };
|
|
75
84
|
}
|
|
85
|
+
async function requireProject(projectId) {
|
|
86
|
+
const explicitProjectId = normalizeProjectId(projectId);
|
|
87
|
+
const resolvedProjectId = explicitProjectId ?? activeProjectId;
|
|
88
|
+
if (!resolvedProjectId) {
|
|
89
|
+
throw new Error('A project is required. Use set_active_project or pass project_id. Run list_projects to see available projects.');
|
|
90
|
+
}
|
|
91
|
+
const context = await getTenantContext();
|
|
92
|
+
const project = context.projects.find((candidate) => candidate.id === resolvedProjectId) ?? null;
|
|
93
|
+
if (!project) {
|
|
94
|
+
activeProjectId = null;
|
|
95
|
+
throw new Error('A project is required. The selected project was not found or is inaccessible. Use set_active_project or pass project_id. Run list_projects to see available projects.');
|
|
96
|
+
}
|
|
97
|
+
return { projectId: resolvedProjectId, project };
|
|
98
|
+
}
|
|
76
99
|
// ── Tool: workspace context ─────────────────────────────────────────
|
|
77
100
|
server.tool('get_workspace_context', 'Show Flow Relay tenant context: personal/business mode, active project scope, and current role.', {}, async () => {
|
|
78
101
|
const context = await getTenantContext();
|
|
@@ -99,7 +122,7 @@ server.tool('list_projects', 'List all projects available to this API key, inclu
|
|
|
99
122
|
return {
|
|
100
123
|
content: [{
|
|
101
124
|
type: 'text',
|
|
102
|
-
text: 'No accessible projects found.
|
|
125
|
+
text: 'No accessible projects found. Create a project in Flow Relay or get added to a project team.',
|
|
103
126
|
}],
|
|
104
127
|
};
|
|
105
128
|
}
|
|
@@ -124,7 +147,7 @@ server.tool('set_active_project', 'Set or clear the active project context used
|
|
|
124
147
|
}, async ({ project_id, clear }) => {
|
|
125
148
|
if (clear || !normalizeProjectId(project_id)) {
|
|
126
149
|
activeProjectId = null;
|
|
127
|
-
return { content: [{ type: 'text', text: 'Active project cleared.
|
|
150
|
+
return { content: [{ type: 'text', text: 'Active project cleared. Handoff/insight generation requires selecting a project.' }] };
|
|
128
151
|
}
|
|
129
152
|
const context = await getTenantContext();
|
|
130
153
|
const selected = context.projects.find((project) => project.id === project_id);
|
|
@@ -145,23 +168,29 @@ server.tool('set_active_project', 'Set or clear the active project context used
|
|
|
145
168
|
};
|
|
146
169
|
});
|
|
147
170
|
// ── Tool: list handoffs ──────────────────────────────────────────────
|
|
148
|
-
server.tool('list_handoffs', 'List Flow Relay handoffs in the current tenant scope (
|
|
149
|
-
status: z.enum(['active', 'archived', '
|
|
171
|
+
server.tool('list_handoffs', 'List Flow Relay handoffs in the current tenant scope (all accessible projects or selected project).', {
|
|
172
|
+
status: z.enum(['active', 'archived', 'all']).default('active').describe('Filter by status ("all" returns every status)'),
|
|
150
173
|
limit: z.number().min(1).max(50).default(10).describe('Max number of handoffs to return'),
|
|
151
174
|
project_id: z.string().optional().describe('Optional project scope override for this call.'),
|
|
152
175
|
}, async ({ status, limit, project_id }) => {
|
|
153
|
-
const
|
|
154
|
-
const
|
|
176
|
+
const explicitProjectId = normalizeProjectId(project_id);
|
|
177
|
+
const resolvedProjectId = explicitProjectId ?? activeProjectId;
|
|
178
|
+
let resolvedProject = null;
|
|
179
|
+
if (resolvedProjectId) {
|
|
180
|
+
const context = await getTenantContext();
|
|
181
|
+
resolvedProject = context.projects.find((p) => p.id === resolvedProjectId) ?? null;
|
|
182
|
+
if (!resolvedProject)
|
|
183
|
+
activeProjectId = null;
|
|
184
|
+
}
|
|
185
|
+
const { handoffs } = await api.listHandoffs(status, limit, resolvedProject?.id ?? null);
|
|
155
186
|
if (handoffs.length === 0) {
|
|
156
|
-
const scopeLabel = formatProjectScope(
|
|
187
|
+
const scopeLabel = resolvedProject ? formatProjectScope(resolvedProject) : 'all projects';
|
|
157
188
|
return { content: [{ type: 'text', text: `No ${status} handoffs found in scope: ${scopeLabel}.` }] };
|
|
158
189
|
}
|
|
159
190
|
const text = handoffs.map((h) => {
|
|
160
191
|
let out = `## ${h.title}\n`;
|
|
161
192
|
out += `**Status:** ${h.status} · **Sources:** ${h.sources.join(', ') || 'all'}\n`;
|
|
162
|
-
|
|
163
|
-
out += `**Project:** ${h.project_name}\n`;
|
|
164
|
-
}
|
|
193
|
+
out += `**Project:** ${h.project_name ?? 'Unknown'}\n`;
|
|
165
194
|
out += `**Created:** ${new Date(h.created_at).toLocaleString()}\n\n`;
|
|
166
195
|
out += `${h.summary}\n`;
|
|
167
196
|
if (h.key_changes?.length)
|
|
@@ -181,28 +210,18 @@ server.tool('generate_handoff', 'Generate a new context handoff for personal sco
|
|
|
181
210
|
sources: z.array(SourceEnum)
|
|
182
211
|
.optional()
|
|
183
212
|
.describe('Specific sources to include (omit for all connected sources)'),
|
|
184
|
-
filters:
|
|
185
|
-
projects: z.array(z.string()).optional().describe('Filter to specific repos, channels, projects, or teams'),
|
|
186
|
-
eventTypes: z.array(z.string()).optional().describe('Filter to specific event types (e.g. "push", "issue_created")'),
|
|
187
|
-
})).optional().describe('Per-source advanced filters'),
|
|
213
|
+
filters: FiltersSchema,
|
|
188
214
|
project_id: z.string().optional().describe('Optional project scope override for this call.'),
|
|
189
215
|
}, async ({ sources, filters, project_id }) => {
|
|
190
216
|
try {
|
|
191
|
-
const resolved = await
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (job.status === 'failed' || !result) {
|
|
198
|
-
const reason = job.error ?? 'unknown error';
|
|
199
|
-
return { content: [{ type: 'text', text: `Could not generate handoff: ${reason}` }] };
|
|
200
|
-
}
|
|
201
|
-
handoff = result;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
handoff = res.handoff;
|
|
217
|
+
const resolved = await requireProject(project_id);
|
|
218
|
+
const { jobId } = await api.generateHandoff(sources, filters, resolved.projectId);
|
|
219
|
+
const { job, result } = await api.waitForJob(jobId);
|
|
220
|
+
if (job.status === 'failed' || !result) {
|
|
221
|
+
const reason = job.error ?? 'unknown error';
|
|
222
|
+
return { content: [{ type: 'text', text: `Could not generate handoff: ${reason}` }] };
|
|
205
223
|
}
|
|
224
|
+
const handoff = result;
|
|
206
225
|
let text = `# ${handoff.title}\n\n${handoff.summary}\n`;
|
|
207
226
|
if (handoff.project_name)
|
|
208
227
|
text += `\n**Project:** ${handoff.project_name}\n`;
|
|
@@ -242,6 +261,32 @@ server.tool('list_integrations', 'List integrations in the current scope. In pro
|
|
|
242
261
|
}).join('\n');
|
|
243
262
|
return { content: [{ type: 'text', text: `**Connected integrations:**\n${text}` }] };
|
|
244
263
|
});
|
|
264
|
+
// ── Tool: list untracked resources ───────────────────────────────────
|
|
265
|
+
server.tool('list_untracked_resources', 'List discovered active resources across all connected user integrations that have not yet been assigned to any project.', {}, async () => {
|
|
266
|
+
try {
|
|
267
|
+
const resources = await api.listUntrackedResources();
|
|
268
|
+
if (resources.length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
content: [{
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: 'All discovered active resources are already tracked in your projects. Good job!',
|
|
273
|
+
}],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const text = resources.map((r) => {
|
|
277
|
+
return `- **[${r.source}]** ${r.resource_name} (type: ${r.resource_type}, id: ${r.resource_id})`;
|
|
278
|
+
}).join('\n');
|
|
279
|
+
return {
|
|
280
|
+
content: [{
|
|
281
|
+
type: 'text',
|
|
282
|
+
text: `**Untracked active resources:**\n${text}\n\n*Note: Map these resources to Flow Relay projects in the web dashboard or CLI to start tracking their events.*`,
|
|
283
|
+
}],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
return { content: [{ type: 'text', text: `Failed to list untracked resources: ${err.message}` }] };
|
|
288
|
+
}
|
|
289
|
+
});
|
|
245
290
|
// ── Tool: list recent events ─────────────────────────────────────────
|
|
246
291
|
server.tool('list_events', 'List recent context events in current scope (personal or selected project).', {
|
|
247
292
|
source: SourceEnum
|
|
@@ -297,12 +342,14 @@ server.tool('discord_send_message', 'Send a message to a Discord channel in your
|
|
|
297
342
|
server.tool('generate_correlation_insight', 'Generate a cross-source correlation AI insight for the specified project scope.', {
|
|
298
343
|
project_id: z.string().describe('The project ID scope to generate the correlation insight for.'),
|
|
299
344
|
sources: z.array(SourceEnum).optional().describe('Filter events to specific sources (e.g. "github", "slack")'),
|
|
345
|
+
filters: FiltersSchema,
|
|
300
346
|
lookback_hours: z.number().optional().describe('Number of hours of activity to analyze'),
|
|
301
347
|
max_events: z.number().optional().describe('Maximum number of events to process'),
|
|
302
|
-
}, async ({ project_id, sources, lookback_hours, max_events }) => {
|
|
348
|
+
}, async ({ project_id, sources, filters, lookback_hours, max_events }) => {
|
|
303
349
|
try {
|
|
304
350
|
const res = await api.generateInsight(project_id, 'correlation', {
|
|
305
351
|
sources,
|
|
352
|
+
filters,
|
|
306
353
|
lookbackHours: lookback_hours,
|
|
307
354
|
maxEvents: max_events,
|
|
308
355
|
});
|
|
@@ -322,14 +369,16 @@ server.tool('generate_correlation_insight', 'Generate a cross-source correlation
|
|
|
322
369
|
server.tool('generate_onboarding_brief', 'Generate an onboarding brief AI insight for the specified project scope.', {
|
|
323
370
|
project_id: z.string().describe('The project ID scope to generate the onboarding brief for.'),
|
|
324
371
|
sources: z.array(SourceEnum).optional().describe('Filter events to specific sources'),
|
|
372
|
+
filters: FiltersSchema,
|
|
325
373
|
new_member_role: z.string().optional().describe('Expected role/focus of the new team member'),
|
|
326
374
|
focus_area: z.string().optional().describe('Specific repository or feature area they will work on'),
|
|
327
375
|
lookback_days: z.number().optional().describe('Number of days of history to review'),
|
|
328
376
|
max_events: z.number().optional().describe('Maximum events to process'),
|
|
329
|
-
}, async ({ project_id, sources, new_member_role, focus_area, lookback_days, max_events }) => {
|
|
377
|
+
}, async ({ project_id, sources, filters, new_member_role, focus_area, lookback_days, max_events }) => {
|
|
330
378
|
try {
|
|
331
379
|
const res = await api.generateInsight(project_id, 'onboarding', {
|
|
332
380
|
sources,
|
|
381
|
+
filters,
|
|
333
382
|
newMemberRole: new_member_role,
|
|
334
383
|
focusArea: focus_area,
|
|
335
384
|
lookbackDays: lookback_days,
|
|
@@ -351,13 +400,15 @@ server.tool('generate_onboarding_brief', 'Generate an onboarding brief AI insigh
|
|
|
351
400
|
server.tool('generate_architecture_insight', 'Generate an architecture insight AI insight for the specified project scope.', {
|
|
352
401
|
project_id: z.string().describe('The project ID scope to generate the architecture insight for.'),
|
|
353
402
|
sources: z.array(SourceEnum).optional().describe('Filter events to specific sources'),
|
|
403
|
+
filters: FiltersSchema,
|
|
354
404
|
focus_question: z.string().optional().describe('Specific architectural question or component to focus on'),
|
|
355
405
|
lookback_days: z.number().optional().describe('Number of days of history to review'),
|
|
356
406
|
max_events: z.number().optional().describe('Maximum events to process'),
|
|
357
|
-
}, async ({ project_id, sources, focus_question, lookback_days, max_events }) => {
|
|
407
|
+
}, async ({ project_id, sources, filters, focus_question, lookback_days, max_events }) => {
|
|
358
408
|
try {
|
|
359
409
|
const res = await api.generateInsight(project_id, 'architecture', {
|
|
360
410
|
sources,
|
|
411
|
+
filters,
|
|
361
412
|
focusQuestion: focus_question,
|
|
362
413
|
lookbackDays: lookback_days,
|
|
363
414
|
maxEvents: max_events,
|
|
@@ -378,7 +429,7 @@ server.tool('generate_architecture_insight', 'Generate an architecture insight A
|
|
|
378
429
|
server.tool('list_insights', 'List project AI insights for a selected project scope.', {
|
|
379
430
|
project_id: z.string().describe('The project ID context to list insights for.'),
|
|
380
431
|
kind: z.enum(['onboarding_brief', 'cross_source_correlation', 'architecture_insight']).optional().describe('Filter by insight kind'),
|
|
381
|
-
status: z.enum(['active', 'archived', '
|
|
432
|
+
status: z.enum(['active', 'archived', 'all']).default('active').describe('Filter by status ("all" returns every status)'),
|
|
382
433
|
limit: z.number().min(1).max(50).default(20).describe('Max number of insights to return'),
|
|
383
434
|
}, async ({ project_id, kind, status, limit }) => {
|
|
384
435
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowrelay/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"homepage": "https://www.flowrelay.it",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/atrisorb/flow-relay.git"
|
|
11
|
+
"url": "git+https://github.com/atrisorb/flow-relay.git"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
14
|
"mcp",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"ai"
|
|
20
20
|
],
|
|
21
21
|
"bin": {
|
|
22
|
-
"flowrelay-mcp": "
|
|
22
|
+
"flowrelay-mcp": "dist/index.js"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist"
|