@flowrelay/mcp-server 0.6.0 → 0.8.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 +9 -2
- package/dist/api.js +4 -2
- package/dist/index.js +58 -12
- 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": "optional_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.8.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)
|
|
22
|
+
- generate_handoff (processed asynchronously, and the server automatically polls until the job finishes. Requires active project or project_id.)
|
|
23
|
+
- generate_correlation_insight
|
|
24
|
+
- generate_onboarding_brief
|
|
25
|
+
- generate_architecture_insight
|
|
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": "optional_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,6 +78,12 @@ 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
|
+
}
|
|
81
87
|
export type GenerateHandoffResponse = {
|
|
82
88
|
handoff: HandoffResult;
|
|
83
89
|
} | {
|
|
@@ -98,10 +104,10 @@ export declare class FlowRelayAPI {
|
|
|
98
104
|
listHandoffs(status?: string, limit?: number, projectId?: string | null): Promise<{
|
|
99
105
|
handoffs: HandoffResult[];
|
|
100
106
|
}>;
|
|
101
|
-
generateHandoff(sources
|
|
107
|
+
generateHandoff(sources: string[] | undefined, filters: Record<string, {
|
|
102
108
|
projects?: string[];
|
|
103
109
|
eventTypes?: string[];
|
|
104
|
-
}
|
|
110
|
+
}> | undefined, projectId: string): Promise<GenerateHandoffResponse>;
|
|
105
111
|
getJob(jobId: string): Promise<{
|
|
106
112
|
job: AiJob;
|
|
107
113
|
result: HandoffResult | InsightResult | null;
|
|
@@ -152,4 +158,5 @@ export declare class FlowRelayAPI {
|
|
|
152
158
|
ok: boolean;
|
|
153
159
|
message_id: string;
|
|
154
160
|
}>;
|
|
161
|
+
listUntrackedResources(): Promise<UntrackedResource[]>;
|
|
155
162
|
}
|
package/dist/api.js
CHANGED
|
@@ -48,8 +48,7 @@ export class FlowRelayAPI {
|
|
|
48
48
|
body.sources = sources;
|
|
49
49
|
if (filters && Object.keys(filters).length > 0)
|
|
50
50
|
body.filters = filters;
|
|
51
|
-
|
|
52
|
-
body.project_id = projectId;
|
|
51
|
+
body.project_id = projectId;
|
|
53
52
|
const { status, body: data } = await this.requestRaw('/handoffs', {
|
|
54
53
|
method: 'POST',
|
|
55
54
|
body: JSON.stringify(body),
|
|
@@ -115,4 +114,7 @@ export class FlowRelayAPI {
|
|
|
115
114
|
body: JSON.stringify({ channel_id: channelId, content }),
|
|
116
115
|
});
|
|
117
116
|
}
|
|
117
|
+
async listUntrackedResources() {
|
|
118
|
+
return this.request('/integrations/untracked');
|
|
119
|
+
}
|
|
118
120
|
}
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ function normalizeProjectId(value) {
|
|
|
29
29
|
}
|
|
30
30
|
function formatProjectScope(project) {
|
|
31
31
|
if (!project)
|
|
32
|
-
return 'No project (
|
|
32
|
+
return 'No project selected (required for handoffs & insights)';
|
|
33
33
|
if (project.project_type === 'personal') {
|
|
34
34
|
return `Personal project: ${project.name}`;
|
|
35
35
|
}
|
|
@@ -50,7 +50,7 @@ const api = new FlowRelayAPI(apiKey, process.env.FLOWRELAY_BASE_URL);
|
|
|
50
50
|
let activeProjectId = normalizeProjectId(process.env.FLOWRELAY_PROJECT_ID);
|
|
51
51
|
const server = new McpServer({
|
|
52
52
|
name: 'flowrelay',
|
|
53
|
-
version: '0.
|
|
53
|
+
version: '0.8.0',
|
|
54
54
|
});
|
|
55
55
|
async function getTenantContext() {
|
|
56
56
|
const context = await api.listProjects();
|
|
@@ -73,6 +73,20 @@ async function resolveProject(projectId) {
|
|
|
73
73
|
}
|
|
74
74
|
return { projectId: resolvedProjectId, project };
|
|
75
75
|
}
|
|
76
|
+
async function requireProject(projectId) {
|
|
77
|
+
const explicitProjectId = normalizeProjectId(projectId);
|
|
78
|
+
const resolvedProjectId = explicitProjectId ?? activeProjectId;
|
|
79
|
+
if (!resolvedProjectId) {
|
|
80
|
+
throw new Error('A project is required. Use set_active_project or pass project_id. Run list_projects to see available projects.');
|
|
81
|
+
}
|
|
82
|
+
const context = await getTenantContext();
|
|
83
|
+
const project = context.projects.find((candidate) => candidate.id === resolvedProjectId) ?? null;
|
|
84
|
+
if (!project) {
|
|
85
|
+
activeProjectId = null;
|
|
86
|
+
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.');
|
|
87
|
+
}
|
|
88
|
+
return { projectId: resolvedProjectId, project };
|
|
89
|
+
}
|
|
76
90
|
// ── Tool: workspace context ─────────────────────────────────────────
|
|
77
91
|
server.tool('get_workspace_context', 'Show Flow Relay tenant context: personal/business mode, active project scope, and current role.', {}, async () => {
|
|
78
92
|
const context = await getTenantContext();
|
|
@@ -99,7 +113,7 @@ server.tool('list_projects', 'List all projects available to this API key, inclu
|
|
|
99
113
|
return {
|
|
100
114
|
content: [{
|
|
101
115
|
type: 'text',
|
|
102
|
-
text: 'No accessible projects found.
|
|
116
|
+
text: 'No accessible projects found. Create a project in Flow Relay or get added to a project team.',
|
|
103
117
|
}],
|
|
104
118
|
};
|
|
105
119
|
}
|
|
@@ -124,7 +138,7 @@ server.tool('set_active_project', 'Set or clear the active project context used
|
|
|
124
138
|
}, async ({ project_id, clear }) => {
|
|
125
139
|
if (clear || !normalizeProjectId(project_id)) {
|
|
126
140
|
activeProjectId = null;
|
|
127
|
-
return { content: [{ type: 'text', text: 'Active project cleared.
|
|
141
|
+
return { content: [{ type: 'text', text: 'Active project cleared. Handoff/insight generation requires selecting a project.' }] };
|
|
128
142
|
}
|
|
129
143
|
const context = await getTenantContext();
|
|
130
144
|
const selected = context.projects.find((project) => project.id === project_id);
|
|
@@ -145,23 +159,29 @@ server.tool('set_active_project', 'Set or clear the active project context used
|
|
|
145
159
|
};
|
|
146
160
|
});
|
|
147
161
|
// ── Tool: list handoffs ──────────────────────────────────────────────
|
|
148
|
-
server.tool('list_handoffs', 'List Flow Relay handoffs in the current tenant scope (
|
|
162
|
+
server.tool('list_handoffs', 'List Flow Relay handoffs in the current tenant scope (all accessible projects or selected project).', {
|
|
149
163
|
status: z.enum(['active', 'archived', 'completed']).default('active').describe('Filter by status'),
|
|
150
164
|
limit: z.number().min(1).max(50).default(10).describe('Max number of handoffs to return'),
|
|
151
165
|
project_id: z.string().optional().describe('Optional project scope override for this call.'),
|
|
152
166
|
}, async ({ status, limit, project_id }) => {
|
|
153
|
-
const
|
|
154
|
-
const
|
|
167
|
+
const explicitProjectId = normalizeProjectId(project_id);
|
|
168
|
+
const resolvedProjectId = explicitProjectId ?? activeProjectId;
|
|
169
|
+
let resolvedProject = null;
|
|
170
|
+
if (resolvedProjectId) {
|
|
171
|
+
const context = await getTenantContext();
|
|
172
|
+
resolvedProject = context.projects.find((p) => p.id === resolvedProjectId) ?? null;
|
|
173
|
+
if (!resolvedProject)
|
|
174
|
+
activeProjectId = null;
|
|
175
|
+
}
|
|
176
|
+
const { handoffs } = await api.listHandoffs(status, limit, resolvedProject?.id ?? null);
|
|
155
177
|
if (handoffs.length === 0) {
|
|
156
|
-
const scopeLabel = formatProjectScope(
|
|
178
|
+
const scopeLabel = resolvedProject ? formatProjectScope(resolvedProject) : 'all projects';
|
|
157
179
|
return { content: [{ type: 'text', text: `No ${status} handoffs found in scope: ${scopeLabel}.` }] };
|
|
158
180
|
}
|
|
159
181
|
const text = handoffs.map((h) => {
|
|
160
182
|
let out = `## ${h.title}\n`;
|
|
161
183
|
out += `**Status:** ${h.status} · **Sources:** ${h.sources.join(', ') || 'all'}\n`;
|
|
162
|
-
|
|
163
|
-
out += `**Project:** ${h.project_name}\n`;
|
|
164
|
-
}
|
|
184
|
+
out += `**Project:** ${h.project_name ?? 'Unknown'}\n`;
|
|
165
185
|
out += `**Created:** ${new Date(h.created_at).toLocaleString()}\n\n`;
|
|
166
186
|
out += `${h.summary}\n`;
|
|
167
187
|
if (h.key_changes?.length)
|
|
@@ -188,7 +208,7 @@ server.tool('generate_handoff', 'Generate a new context handoff for personal sco
|
|
|
188
208
|
project_id: z.string().optional().describe('Optional project scope override for this call.'),
|
|
189
209
|
}, async ({ sources, filters, project_id }) => {
|
|
190
210
|
try {
|
|
191
|
-
const resolved = await
|
|
211
|
+
const resolved = await requireProject(project_id);
|
|
192
212
|
const res = await api.generateHandoff(sources, filters, resolved.projectId);
|
|
193
213
|
// Async path (project handoff): poll until the worker finishes.
|
|
194
214
|
let handoff;
|
|
@@ -242,6 +262,32 @@ server.tool('list_integrations', 'List integrations in the current scope. In pro
|
|
|
242
262
|
}).join('\n');
|
|
243
263
|
return { content: [{ type: 'text', text: `**Connected integrations:**\n${text}` }] };
|
|
244
264
|
});
|
|
265
|
+
// ── Tool: list untracked resources ───────────────────────────────────
|
|
266
|
+
server.tool('list_untracked_resources', 'List discovered active resources across all connected user integrations that have not yet been assigned to any project.', {}, async () => {
|
|
267
|
+
try {
|
|
268
|
+
const resources = await api.listUntrackedResources();
|
|
269
|
+
if (resources.length === 0) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: 'All discovered active resources are already tracked in your projects. Good job!',
|
|
274
|
+
}],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const text = resources.map((r) => {
|
|
278
|
+
return `- **[${r.source}]** ${r.resource_name} (type: ${r.resource_type}, id: ${r.resource_id})`;
|
|
279
|
+
}).join('\n');
|
|
280
|
+
return {
|
|
281
|
+
content: [{
|
|
282
|
+
type: 'text',
|
|
283
|
+
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.*`,
|
|
284
|
+
}],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
return { content: [{ type: 'text', text: `Failed to list untracked resources: ${err.message}` }] };
|
|
289
|
+
}
|
|
290
|
+
});
|
|
245
291
|
// ── Tool: list recent events ─────────────────────────────────────────
|
|
246
292
|
server.tool('list_events', 'List recent context events in current scope (personal or selected project).', {
|
|
247
293
|
source: SourceEnum
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowrelay/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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"
|