@flowrelay/mcp-server 0.1.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/dist/api.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Flow Relay API client — talks to flowrelay.it/api/v1/*
3
+ */
4
+ export declare class FlowRelayAPI {
5
+ private baseUrl;
6
+ private apiKey;
7
+ constructor(apiKey: string, baseUrl?: string);
8
+ private request;
9
+ listHandoffs(status?: string, limit?: number): Promise<{
10
+ handoffs: Array<{
11
+ id: string;
12
+ title: string;
13
+ summary: string;
14
+ status: string;
15
+ sources: string[];
16
+ decisions: string[];
17
+ open_questions: string[];
18
+ next_steps: string[];
19
+ created_at: string;
20
+ updated_at: string;
21
+ }>;
22
+ }>;
23
+ generateHandoff(sources?: string[]): Promise<{
24
+ handoff: {
25
+ id: string;
26
+ title: string;
27
+ summary: string;
28
+ sources: string[];
29
+ decisions: string[];
30
+ open_questions: string[];
31
+ next_steps: string[];
32
+ created_at: string;
33
+ };
34
+ }>;
35
+ listIntegrations(): Promise<{
36
+ integrations: Array<{
37
+ source: string;
38
+ workspace_id: string | null;
39
+ workspace_name: string | null;
40
+ connected_at: string;
41
+ }>;
42
+ }>;
43
+ listEvents(source?: string, limit?: number): Promise<{
44
+ events: Array<{
45
+ id: string;
46
+ source: string;
47
+ event_type: string;
48
+ title: string;
49
+ content: string;
50
+ created_at: string;
51
+ }>;
52
+ }>;
53
+ }
package/dist/api.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Flow Relay API client — talks to flowrelay.it/api/v1/*
3
+ */
4
+ const DEFAULT_BASE_URL = 'https://www.flowrelay.it';
5
+ export class FlowRelayAPI {
6
+ baseUrl;
7
+ apiKey;
8
+ constructor(apiKey, baseUrl) {
9
+ this.apiKey = apiKey;
10
+ this.baseUrl = baseUrl ?? DEFAULT_BASE_URL;
11
+ }
12
+ async request(path, options) {
13
+ const url = `${this.baseUrl}/api/v1${path}`;
14
+ const res = await fetch(url, {
15
+ ...options,
16
+ headers: {
17
+ 'Authorization': `Bearer ${this.apiKey}`,
18
+ 'Content-Type': 'application/json',
19
+ ...options?.headers,
20
+ },
21
+ });
22
+ if (!res.ok) {
23
+ const body = await res.json().catch(() => ({ error: res.statusText }));
24
+ throw new Error(body.error ?? `API error ${res.status}`);
25
+ }
26
+ return res.json();
27
+ }
28
+ async listHandoffs(status = 'active', limit = 10) {
29
+ return this.request(`/handoffs?status=${status}&limit=${limit}`);
30
+ }
31
+ async generateHandoff(sources) {
32
+ return this.request('/handoffs', {
33
+ method: 'POST',
34
+ body: JSON.stringify(sources?.length ? { sources } : {}),
35
+ });
36
+ }
37
+ async listIntegrations() {
38
+ return this.request('/integrations');
39
+ }
40
+ async listEvents(source, limit = 20) {
41
+ const params = new URLSearchParams();
42
+ if (source)
43
+ params.set('source', source);
44
+ params.set('limit', String(limit));
45
+ return this.request(`/events?${params}`);
46
+ }
47
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { FlowRelayAPI } from './api.js';
6
+ const apiKey = process.env.FLOWRELAY_API_KEY;
7
+ if (!apiKey) {
8
+ console.error('Error: FLOWRELAY_API_KEY environment variable is required.');
9
+ console.error('Create an API key at https://www.flowrelay.it/settings');
10
+ process.exit(1);
11
+ }
12
+ const api = new FlowRelayAPI(apiKey, process.env.FLOWRELAY_BASE_URL);
13
+ const server = new McpServer({
14
+ name: 'flowrelay',
15
+ version: '0.1.0',
16
+ });
17
+ // ── Tool: list handoffs ──────────────────────────────────────────────
18
+ server.tool('list_handoffs', 'List your Flow Relay handoffs. Returns recent handoffs with summaries, decisions, and next steps.', {
19
+ status: z.enum(['active', 'archived', 'completed']).default('active').describe('Filter by status'),
20
+ limit: z.number().min(1).max(50).default(10).describe('Max number of handoffs to return'),
21
+ }, async ({ status, limit }) => {
22
+ const { handoffs } = await api.listHandoffs(status, limit);
23
+ if (handoffs.length === 0) {
24
+ return { content: [{ type: 'text', text: `No ${status} handoffs found.` }] };
25
+ }
26
+ const text = handoffs.map((h) => {
27
+ let out = `## ${h.title}\n`;
28
+ out += `**Status:** ${h.status} · **Sources:** ${h.sources.join(', ') || 'all'}\n`;
29
+ out += `**Created:** ${new Date(h.created_at).toLocaleString()}\n\n`;
30
+ out += `${h.summary}\n`;
31
+ if (h.decisions.length)
32
+ out += `\n**Decisions:**\n${h.decisions.map((d) => `- ${d}`).join('\n')}\n`;
33
+ if (h.next_steps.length)
34
+ out += `\n**Next steps:**\n${h.next_steps.map((s) => `- ${s}`).join('\n')}\n`;
35
+ if (h.open_questions.length)
36
+ out += `\n**Open questions:**\n${h.open_questions.map((q) => `- ${q}`).join('\n')}\n`;
37
+ return out;
38
+ }).join('\n---\n\n');
39
+ return { content: [{ type: 'text', text }] };
40
+ });
41
+ // ── Tool: generate handoff ───────────────────────────────────────────
42
+ server.tool('generate_handoff', 'Generate a new context handoff from your connected integrations. Summarizes recent activity into decisions, open questions, and next steps.', {
43
+ sources: z.array(z.enum(['github', 'slack', 'linear', 'notion', 'jira', 'gitlab']))
44
+ .optional()
45
+ .describe('Specific sources to include (omit for all connected sources)'),
46
+ }, async ({ sources }) => {
47
+ try {
48
+ const { handoff } = await api.generateHandoff(sources);
49
+ let text = `# ${handoff.title}\n\n${handoff.summary}\n`;
50
+ text += `\n**Sources:** ${handoff.sources.join(', ')}\n`;
51
+ if (handoff.decisions.length)
52
+ text += `\n**Decisions:**\n${handoff.decisions.map((d) => `- ${d}`).join('\n')}\n`;
53
+ if (handoff.next_steps.length)
54
+ text += `\n**Next steps:**\n${handoff.next_steps.map((s) => `- ${s}`).join('\n')}\n`;
55
+ return { content: [{ type: 'text', text }] };
56
+ }
57
+ catch (err) {
58
+ return { content: [{ type: 'text', text: `Could not generate handoff: ${err.message}` }] };
59
+ }
60
+ });
61
+ // ── Tool: list integrations ─────────────────────────────────────────
62
+ server.tool('list_integrations', 'List your connected Flow Relay integrations (GitHub, Slack, Linear, Notion, Jira, GitLab).', {}, async () => {
63
+ const { integrations } = await api.listIntegrations();
64
+ if (integrations.length === 0) {
65
+ return { content: [{ type: 'text', text: 'No integrations connected. Visit https://www.flowrelay.it/integrations to set up.' }] };
66
+ }
67
+ const text = integrations.map((i) => {
68
+ const name = i.workspace_name ? ` (${i.workspace_name})` : '';
69
+ return `- **${i.source}**${name} — connected ${new Date(i.connected_at).toLocaleDateString()}`;
70
+ }).join('\n');
71
+ return { content: [{ type: 'text', text: `**Connected integrations:**\n${text}` }] };
72
+ });
73
+ // ── Tool: list recent events ─────────────────────────────────────────
74
+ server.tool('list_events', 'List recent context events (commits, messages, issues, etc.) from your connected integrations.', {
75
+ source: z.enum(['github', 'slack', 'linear', 'notion', 'jira', 'gitlab'])
76
+ .optional()
77
+ .describe('Filter by integration source'),
78
+ limit: z.number().min(1).max(100).default(20).describe('Max number of events'),
79
+ }, async ({ source, limit }) => {
80
+ const { events } = await api.listEvents(source, limit);
81
+ if (events.length === 0) {
82
+ return { content: [{ type: 'text', text: `No recent events${source ? ` from ${source}` : ''}.` }] };
83
+ }
84
+ const text = events.map((e) => {
85
+ const time = new Date(e.created_at).toLocaleString();
86
+ return `- **[${e.source}/${e.event_type}]** ${e.title} _(${time})_`;
87
+ }).join('\n');
88
+ return { content: [{ type: 'text', text: `**Recent events:**\n${text}` }] };
89
+ });
90
+ // ── Start ────────────────────────────────────────────────────────────
91
+ const transport = new StdioServerTransport();
92
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@flowrelay/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Flow Relay MCP Server for Claude Desktop and Claude Code — handoffs, integrations, and context events via natural conversation.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Adriano Sorbello",
8
+ "homepage": "https://www.flowrelay.it",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/AdrianSorbello/flow-relay"
12
+ },
13
+ "keywords": ["mcp", "claude", "flow-relay", "context", "handoff", "ai"],
14
+ "bin": {
15
+ "flowrelay-mcp": "./dist/index.js"
16
+ },
17
+ "files": ["dist"],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "zod": "^3.24.0"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^5.7.0",
29
+ "@types/node": "^20"
30
+ }
31
+ }