@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 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 both:
6
-
7
- - Personal stream scope
8
- - Project scope (personal project or organization project)
9
-
10
- ## Package
11
-
12
- - Name: @flowrelay/mcp-server
13
- - Version: 0.6.0
14
-
15
- ## What Is Included
16
-
17
- The server currently exposes these tools:
18
-
19
- - get_workspace_context
20
- - list_projects
21
- - set_active_project
22
- - list_handoffs
23
- - generate_handoff (Note: Personal stream handoffs are synchronous. Project-scoped handoffs are processed asynchronously, and the server automatically polls until the job finishes.)
24
- - generate_correlation_insight
25
- - generate_onboarding_brief
26
- - generate_architecture_insight
27
- - list_insights
28
- - list_integrations
29
- - list_events
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
- - If no project is active, tools run in personal stream scope.
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?: string[], filters?: Record<string, {
107
+ generateHandoff(sources: string[] | undefined, filters: Record<string, {
102
108
  projects?: string[];
103
109
  eventTypes?: string[];
104
- }>, projectId?: string | null): Promise<GenerateHandoffResponse>;
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
- if (projectId)
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 (personal stream)';
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.6.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. Use personal stream or get added to a project team.',
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. Using no-project personal scope.' }] };
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 (personal stream or selected project).', {
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 resolved = await resolveProject(project_id);
154
- const { handoffs } = await api.listHandoffs(status, limit, resolved.projectId);
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(resolved.project);
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
- if (h.project_name) {
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 resolveProject(project_id);
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.6.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": "./dist/index.js"
22
+ "flowrelay-mcp": "dist/index.js"
23
23
  },
24
24
  "files": [
25
25
  "dist"