@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 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.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?: string[], filters?: Record<string, {
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
- if (projectId)
52
- body.project_id = projectId;
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 (personal stream)';
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.6.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. Use personal stream or get added to a project team.',
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. Using no-project personal scope.' }] };
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 (personal stream or selected project).', {
149
- status: z.enum(['active', 'archived', 'completed']).default('active').describe('Filter by status'),
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 resolved = await resolveProject(project_id);
154
- const { handoffs } = await api.listHandoffs(status, limit, resolved.projectId);
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(resolved.project);
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
- if (h.project_name) {
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: z.record(z.string(), z.object({
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 resolveProject(project_id);
192
- const res = await api.generateHandoff(sources, filters, resolved.projectId);
193
- // Async path (project handoff): poll until the worker finishes.
194
- let handoff;
195
- if ('jobId' in res) {
196
- const { job, result } = await api.waitForJob(res.jobId);
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', 'completed']).default('active').describe('Filter by status'),
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.6.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": "./dist/index.js"
22
+ "flowrelay-mcp": "dist/index.js"
23
23
  },
24
24
  "files": [
25
25
  "dist"