|
1 |
| -import { FlowcoreClient } from "@flowcore/sdk"; |
2 |
| -import { OidcClient } from "@flowcore/sdk-oidc-client"; |
| 1 | +import { FlowcoreClient } from "@flowcore/sdk" |
| 2 | +import { OidcClient } from "@flowcore/sdk-oidc-client" |
| 3 | +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js" |
| 4 | +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" |
| 5 | +import { parseArgs } from "node:util" |
| 6 | +import { z } from "zod" |
| 7 | +import { dataCoreResource, eventTypeResource, flowTypeResource, tenantResource } from "./resources" |
3 | 8 | import {
|
4 |
| - McpServer, |
5 |
| - ResourceTemplate, |
6 |
| -} from "@modelcontextprotocol/sdk/server/mcp.js"; |
7 |
| -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; |
8 |
| -import { parseArgs } from "node:util"; |
9 |
| -import { z } from "zod"; |
10 |
| -import { |
11 |
| - dataCoreResource, |
12 |
| - eventTypeResource, |
13 |
| - flowTypeResource, |
14 |
| - tenantResource, |
15 |
| -} from "./resources"; |
16 |
| -import { |
17 |
| - getEventTypeInfoHandler, |
18 |
| - getEventsHandler, |
19 |
| - getTimeBucketsHandler, |
20 |
| - listDataCoresHandler, |
21 |
| - listEventTypesHandler, |
22 |
| - listFlowTypesHandler, |
23 |
| - listTenantsHandler, |
24 |
| -} from "./tools"; |
25 |
| - |
26 |
| -const OIDC_ISSUER = |
27 |
| - "https://auth.flowcore.io/realms/flowcore/.well-known/openid-configuration"; |
28 |
| - |
29 |
| -export async function startServer() { |
30 |
| - // Parse command line arguments |
31 |
| - const { values, positionals } = parseArgs({ |
32 |
| - args: process.argv.slice(2), |
33 |
| - options: { |
34 |
| - serviceAccountId: { type: "string" }, |
35 |
| - serviceAccountKey: { type: "string" }, |
36 |
| - }, |
37 |
| - allowPositionals: true, |
38 |
| - }); |
39 |
| - |
40 |
| - // Log positional arguments if any (for debugging) |
41 |
| - if (positionals.length > 0) { |
42 |
| - console.warn( |
43 |
| - `Warning: Unexpected positional arguments: ${positionals.join(", ")}`, |
44 |
| - ); |
45 |
| - } |
46 |
| - |
47 |
| - const serviceAccountId = values.serviceAccountId as string; |
48 |
| - const serviceAccountKey = values.serviceAccountKey as string; |
49 |
| - |
50 |
| - if (!serviceAccountId || !serviceAccountKey) { |
51 |
| - console.error("Error: No service account credentials provided"); |
52 |
| - console.error( |
53 |
| - "Usage: npx @flowcore/platform-mcp-server --serviceAccountId <accountid> --serviceAccountKey <key>", |
54 |
| - ); |
55 |
| - process.exit(1); |
56 |
| - } |
57 |
| - |
58 |
| - const oidcClient = new OidcClient( |
59 |
| - serviceAccountId, |
60 |
| - serviceAccountKey, |
61 |
| - OIDC_ISSUER, |
62 |
| - ); |
63 |
| - const flowcoreClient = new FlowcoreClient({ |
64 |
| - getBearerToken: async () => { |
65 |
| - const token = await oidcClient.getToken(); |
66 |
| - return token.accessToken; |
67 |
| - }, |
68 |
| - }); |
69 |
| - |
70 |
| - const server = new McpServer({ |
71 |
| - name: "Flowcore Platform", |
72 |
| - version: "1.0.0", |
73 |
| - description: |
74 |
| - "An MCP server for managing and interacting with Flowcore Platform. For information on the details of the flowcore platform, you can check the Flowcore Platform Data Core, as it houses all actions that have happened in the platform. These actions are called events and are the main building blocks of the platform and housed within the data core inside the event type. The hirearchy of the platform is as follows: Users -> Tenant -> Data Core -> Flow Type -> Event Type -> Events. Tenants and organizations are the same thing in the platform, we are transitioning to use the term tenant. The events are stored in time buckets, and can be fetched by using the get_time_buckets tool.", |
75 |
| - }); |
76 |
| - |
77 |
| - server.tool( |
78 |
| - "list_tenants", |
79 |
| - "List all tenants I have access to", |
80 |
| - listTenantsHandler(flowcoreClient), |
81 |
| - ); |
82 |
| - server.tool( |
83 |
| - "list_data_cores", |
84 |
| - "List all data cores for a tenant", |
85 |
| - { tenantId: z.string().describe("The tenant ID to list data cores for") }, |
86 |
| - listDataCoresHandler(flowcoreClient), |
87 |
| - ); |
88 |
| - |
89 |
| - server.tool( |
90 |
| - "list_flow_types", |
91 |
| - "List all flow types for a data core", |
92 |
| - { |
93 |
| - dataCoreId: z |
94 |
| - .string() |
95 |
| - .describe("The data core ID to list flow types for"), |
96 |
| - }, |
97 |
| - listFlowTypesHandler(flowcoreClient), |
98 |
| - ); |
99 |
| - |
100 |
| - server.tool( |
101 |
| - "list_event_types", |
102 |
| - "List all event types for a flow type", |
103 |
| - { |
104 |
| - flowTypeId: z |
105 |
| - .string() |
106 |
| - .describe("The flow type ID to list event types for"), |
107 |
| - }, |
108 |
| - listEventTypesHandler(flowcoreClient), |
109 |
| - ); |
110 |
| - |
111 |
| - server.tool( |
112 |
| - "get_event_type_info", |
113 |
| - "Get event information about an event type, like first and last time bucket and 5 example events", |
114 |
| - { |
115 |
| - eventTypeId: z |
116 |
| - .string() |
117 |
| - .describe("The event type ID to get information for"), |
118 |
| - }, |
119 |
| - getEventTypeInfoHandler(flowcoreClient), |
120 |
| - ); |
121 |
| - |
122 |
| - server.tool( |
123 |
| - "get_events", |
124 |
| - "Get events for an event type, this can be paginated by using the cursor returned from the previous call. This is good for getting the payload of the events to inspect them.", |
125 |
| - { |
126 |
| - eventTypeId: z.string().describe("The event type ID to get events for"), |
127 |
| - timeBucket: z |
128 |
| - .string() |
129 |
| - .describe( |
130 |
| - "The time bucket to get events from, the timebucket is in the format of YYYYMMDDhhiiss, normally the ii and ss are 0000", |
131 |
| - ), |
132 |
| - cursor: z |
133 |
| - .string() |
134 |
| - .optional() |
135 |
| - .describe("The paging cursor for pagination"), |
136 |
| - pageSize: z |
137 |
| - .number() |
138 |
| - .optional() |
139 |
| - .describe("The number of events per page (default is 10,000)"), |
140 |
| - fromEventId: z.string().optional().describe("Start from this event ID"), |
141 |
| - afterEventId: z |
142 |
| - .string() |
143 |
| - .optional() |
144 |
| - .describe( |
145 |
| - "Get events after this event ID (not applicable if fromEventId is defined)", |
146 |
| - ), |
147 |
| - toEventId: z.string().optional().describe("End at this event ID"), |
148 |
| - order: z |
149 |
| - .enum(["asc", "desc"]) |
150 |
| - .optional() |
151 |
| - .describe( |
152 |
| - "The order of events (asc or desc). When using desc, pagination and filters are not possible", |
153 |
| - ), |
154 |
| - }, |
155 |
| - getEventsHandler(flowcoreClient), |
156 |
| - ); |
157 |
| - |
158 |
| - server.tool( |
159 |
| - "get_time_buckets", |
160 |
| - "Get time buckets for an event type, this is useful for getting the time buckets for an event type, and then using the get_events tool to get the events for a specific time bucket. The time bucket is in the format of YYYYMMDDhhiiss, normally the ii and ss are 0000. It can be paginated by using the cursor returned from the previous call.", |
161 |
| - { |
162 |
| - eventTypeId: z |
163 |
| - .string() |
164 |
| - .describe("The event type ID to get time buckets for"), |
165 |
| - fromTimeBucket: z |
166 |
| - .string() |
167 |
| - .optional() |
168 |
| - .describe("Start from this time bucket"), |
169 |
| - toTimeBucket: z.string().optional().describe("End at this time bucket"), |
170 |
| - pageSize: z |
171 |
| - .number() |
172 |
| - .optional() |
173 |
| - .describe("Number of time buckets per page"), |
174 |
| - cursor: z.number().optional().describe("Pagination cursor"), |
175 |
| - order: z |
176 |
| - .enum(["asc", "desc"]) |
177 |
| - .optional() |
178 |
| - .describe("Sort order (asc or desc)"), |
179 |
| - }, |
180 |
| - getTimeBucketsHandler(flowcoreClient), |
181 |
| - ); |
182 |
| - |
183 |
| - server.resource( |
184 |
| - "tenant", |
185 |
| - new ResourceTemplate("tenant://{tenantId}", { list: undefined }), |
186 |
| - tenantResource(flowcoreClient), |
187 |
| - ); |
188 |
| - |
189 |
| - server.resource( |
190 |
| - "data_core", |
191 |
| - new ResourceTemplate("data-core://{dataCoreId}", { list: undefined }), |
192 |
| - dataCoreResource(flowcoreClient), |
193 |
| - ); |
194 |
| - |
195 |
| - server.resource( |
196 |
| - "flow_type", |
197 |
| - new ResourceTemplate("flow-type://{flowTypeId}", { list: undefined }), |
198 |
| - flowTypeResource(flowcoreClient), |
199 |
| - ); |
200 |
| - |
201 |
| - server.resource( |
202 |
| - "event_type", |
203 |
| - new ResourceTemplate("event-type://{eventTypeId}", { list: undefined }), |
204 |
| - eventTypeResource(flowcoreClient), |
205 |
| - ); |
206 |
| - |
207 |
| - // Start receiving messages on stdin and sending messages on stdout |
208 |
| - const transport = new StdioServerTransport(); |
209 |
| - await server.connect(transport); |
| 9 | + getEventTypeInfoHandler, |
| 10 | + getEventsHandler, |
| 11 | + getTimeBucketsHandler, |
| 12 | + listDataCoresHandler, |
| 13 | + listEventTypesHandler, |
| 14 | + listFlowTypesHandler, |
| 15 | + listTenantsHandler, |
| 16 | +} from "./tools" |
| 17 | + |
| 18 | +const OIDC_ISSUER = "https://auth.flowcore.io/realms/flowcore/.well-known/openid-configuration" |
| 19 | + |
| 20 | +// Parse command line arguments |
| 21 | +const { values, positionals } = parseArgs({ |
| 22 | + args: Bun.argv, |
| 23 | + options: { |
| 24 | + serviceAccountId: { type: "string" }, |
| 25 | + serviceAccountKey: { type: "string" }, |
| 26 | + }, |
| 27 | + allowPositionals: true, |
| 28 | +}) |
| 29 | + |
| 30 | +// Log positional arguments if any (for debugging) |
| 31 | +if (positionals.length > 0) { |
| 32 | + console.warn(`Warning: Unexpected positional arguments: ${positionals.join(", ")}`) |
| 33 | +} |
| 34 | + |
| 35 | +const serviceAccountId = values.serviceAccountId as string |
| 36 | +const serviceAccountKey = values.serviceAccountKey as string |
| 37 | + |
| 38 | +if (!serviceAccountId || !serviceAccountKey) { |
| 39 | + throw new Error("No service account credentials provided") |
210 | 40 | }
|
| 41 | + |
| 42 | +const oidcClient = new OidcClient(serviceAccountId, serviceAccountKey, OIDC_ISSUER) |
| 43 | +const flowcoreClient = new FlowcoreClient({ |
| 44 | + getBearerToken: async () => { |
| 45 | + const token = await oidcClient.getToken() |
| 46 | + return token.accessToken |
| 47 | + }, |
| 48 | +}) |
| 49 | + |
| 50 | +// Create an MCP server |
| 51 | +const server = new McpServer({ |
| 52 | + name: "Flowcore Platform", |
| 53 | + version: "1.0.0", |
| 54 | + description: |
| 55 | + "An MCP server for managing and interacting with Flowcore Platform. For information on the details of the flowcore platform, you can check the Flowcore Platform Data Core, as it houses all actions that have happened in the platform. These actions are called events and are the main building blocks of the platform and housed within the data core inside the event type. The hirearchy of the platform is as follows: Users -> Tenant -> Data Core -> Flow Type -> Event Type -> Events. Tenants and organizations are the same thing in the platform, we are transitioning to use the term tenant. The events are stored in time buckets, and can be fetched by using the get_time_buckets tool.", |
| 56 | +}) |
| 57 | + |
| 58 | +server.tool("list_tenants", "List all tenants I have access to", listTenantsHandler(flowcoreClient)) |
| 59 | +server.tool( |
| 60 | + "list_data_cores", |
| 61 | + "List all data cores for a tenant", |
| 62 | + { tenantId: z.string().describe("The tenant ID to list data cores for") }, |
| 63 | + listDataCoresHandler(flowcoreClient), |
| 64 | +) |
| 65 | + |
| 66 | +server.tool( |
| 67 | + "list_flow_types", |
| 68 | + "List all flow types for a data core", |
| 69 | + { |
| 70 | + dataCoreId: z.string().describe("The data core ID to list flow types for"), |
| 71 | + }, |
| 72 | + listFlowTypesHandler(flowcoreClient), |
| 73 | +) |
| 74 | + |
| 75 | +server.tool( |
| 76 | + "list_event_types", |
| 77 | + "List all event types for a flow type", |
| 78 | + { |
| 79 | + flowTypeId: z.string().describe("The flow type ID to list event types for"), |
| 80 | + }, |
| 81 | + listEventTypesHandler(flowcoreClient), |
| 82 | +) |
| 83 | + |
| 84 | +server.tool( |
| 85 | + "get_event_type_info", |
| 86 | + "Get event information about an event type, like first and last time bucket and 5 example events", |
| 87 | + { |
| 88 | + eventTypeId: z.string().describe("The event type ID to get information for"), |
| 89 | + }, |
| 90 | + getEventTypeInfoHandler(flowcoreClient), |
| 91 | +) |
| 92 | + |
| 93 | +server.tool( |
| 94 | + "get_events", |
| 95 | + "Get events for an event type, this can be paginated by using the cursor returned from the previous call. This is good for getting the payload of the events to inspect them.", |
| 96 | + { |
| 97 | + eventTypeId: z.string().describe("The event type ID to get events for"), |
| 98 | + timeBucket: z |
| 99 | + .string() |
| 100 | + .describe( |
| 101 | + "The time bucket to get events from, the timebucket is in the format of YYYYMMDDhhiiss, normally the ii and ss are 0000", |
| 102 | + ), |
| 103 | + cursor: z.string().optional().describe("The paging cursor for pagination"), |
| 104 | + pageSize: z.number().optional().describe("The number of events per page (default is 10,000)"), |
| 105 | + fromEventId: z.string().optional().describe("Start from this event ID"), |
| 106 | + afterEventId: z |
| 107 | + .string() |
| 108 | + .optional() |
| 109 | + .describe("Get events after this event ID (not applicable if fromEventId is defined)"), |
| 110 | + toEventId: z.string().optional().describe("End at this event ID"), |
| 111 | + order: z |
| 112 | + .enum(["asc", "desc"]) |
| 113 | + .optional() |
| 114 | + .describe("The order of events (asc or desc). When using desc, pagination and filters are not possible"), |
| 115 | + }, |
| 116 | + getEventsHandler(flowcoreClient), |
| 117 | +) |
| 118 | + |
| 119 | +server.tool( |
| 120 | + "get_time_buckets", |
| 121 | + "Get time buckets for an event type, this is useful for getting the time buckets for an event type, and then using the get_events tool to get the events for a specific time bucket. The time bucket is in the format of YYYYMMDDhhiiss, normally the ii and ss are 0000. It can be paginated by using the cursor returned from the previous call.", |
| 122 | + { |
| 123 | + eventTypeId: z.string().describe("Event type ID to get time buckets for"), |
| 124 | + fromTimeBucket: z.string().optional().describe("Start time bucket (YYYYMMDDhhiiss)"), |
| 125 | + toTimeBucket: z.string().optional().describe("End time bucket (YYYYMMDDhhiiss)"), |
| 126 | + pageSize: z.number().optional().describe("Number of time buckets per page"), |
| 127 | + cursor: z.number().optional().describe("Pagination cursor"), |
| 128 | + order: z.enum(["asc", "desc"]).optional().describe("Sort order"), |
| 129 | + }, |
| 130 | + getTimeBucketsHandler(flowcoreClient), |
| 131 | +) |
| 132 | + |
| 133 | +server.resource( |
| 134 | + "tenant", |
| 135 | + new ResourceTemplate("tenant://{tenantId}", { list: undefined }), |
| 136 | + tenantResource(flowcoreClient), |
| 137 | +) |
| 138 | + |
| 139 | +server.resource( |
| 140 | + "data_core", |
| 141 | + new ResourceTemplate("data-core://{dataCoreId}", { list: undefined }), |
| 142 | + dataCoreResource(flowcoreClient), |
| 143 | +) |
| 144 | + |
| 145 | +server.resource( |
| 146 | + "flow_type", |
| 147 | + new ResourceTemplate("flow-type://{flowTypeId}", { list: undefined }), |
| 148 | + flowTypeResource(flowcoreClient), |
| 149 | +) |
| 150 | + |
| 151 | +server.resource( |
| 152 | + "event_type", |
| 153 | + new ResourceTemplate("event-type://{eventTypeId}", { list: undefined }), |
| 154 | + eventTypeResource(flowcoreClient), |
| 155 | +) |
| 156 | + |
| 157 | +// Start receiving messages on stdin and sending messages on stdout |
| 158 | +const transport = new StdioServerTransport() |
| 159 | +await server.connect(transport) |
0 commit comments