@flink-app/api-docs-plugin 0.12.1-alpha.4 → 0.12.1-alpha.40

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/BUILD.md ADDED
@@ -0,0 +1,52 @@
1
+ # Build Process for API Docs Plugin
2
+
3
+ This plugin consists of two main parts:
4
+ 1. The TypeScript plugin code
5
+ 2. A React application for the documentation UI
6
+
7
+ ## Directory Structure
8
+
9
+ ```
10
+ api-docs-plugin/
11
+ ├── src/ # TypeScript plugin source
12
+ ├── react-app/ # React application source
13
+ │ ├── src/ # React components
14
+ │ └── dist/ # Built React files (after build)
15
+ └── dist/ # Compiled plugin output
16
+ ├── index.js # Compiled plugin
17
+ └── react-app/ # Copied React build files
18
+ ```
19
+
20
+ ## Build Process
21
+
22
+ The build process follows these steps:
23
+
24
+ 1. **Build React App**:
25
+ - Runs `npm run build` in the `react-app` directory
26
+ - Outputs files to `react-app/dist`
27
+
28
+ 2. **Compile TypeScript**:
29
+ - Compiles all TypeScript files from `src/` to `dist/`
30
+
31
+ 3. **Copy Assets**:
32
+ - Copies the built React app from `react-app/dist` to `dist/react-app`
33
+
34
+ ## Commands
35
+
36
+ - `npm run prepare`: Runs the full build process (React + TypeScript + copy)
37
+ - `npm run build:react`: Builds only the React app
38
+ - `npm run clean`: Removes the dist directory
39
+
40
+ ## How It Works
41
+
42
+ 1. The plugin serves the React app from `dist/react-app` at the `/docs` endpoint
43
+ 2. The React app fetches API data from `/docs/api`
44
+ 3. The plugin provides the API data based on registered Flink handlers
45
+
46
+ ## Development
47
+
48
+ For development, you can run:
49
+ - `npm run dev`: Starts a development server
50
+ - `npm run watch`: Watches for TypeScript changes
51
+
52
+ Make sure to build the React app before testing the full plugin.
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,64 @@
1
+ # Development Guide for API Docs Plugin
2
+
3
+ This guide explains how to develop the API Docs Plugin React app.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js installed (see root .nvmrc for version)
8
+ - Dependencies installed in both the plugin root and react-app directories:
9
+ ```bash
10
+ npm install
11
+ cd react-app && npm install
12
+ ```
13
+
14
+ ## Development Setup
15
+
16
+ The development setup consists of two servers:
17
+
18
+ 1. **Mock API Server** (port 3001): Serves sample API data
19
+ 2. **Vite Dev Server** (port 5173): Serves the React app with hot-reloading
20
+
21
+ ## Running the Development Environment
22
+
23
+ 1. Start the mock API server:
24
+ ```bash
25
+ npm run dev:mock-api
26
+ ```
27
+
28
+ 2. In another terminal, start the React dev server:
29
+ ```bash
30
+ npm run dev:react
31
+ ```
32
+
33
+ 3. Open http://localhost:5173 in your browser
34
+
35
+ The React app will fetch data from the mock API server through Vite's proxy configuration.
36
+
37
+ ## Available Scripts
38
+
39
+ | Command | Description |
40
+ |---------|-------------|
41
+ | `npm run dev:mock-api` | Start the mock API server |
42
+ | `npm run dev:react` | Start the React development server |
43
+ | `npm run build:react` | Build the React app for production |
44
+ | `npm run prepare` | Build everything for production |
45
+ | `npm run clean` | Remove all build artifacts |
46
+
47
+ ## Modifying Sample Data
48
+
49
+ To test different API structures, edit `sample.json` and restart the mock API server.
50
+
51
+ ## Building for Production
52
+
53
+ To build the complete plugin:
54
+
55
+ ```bash
56
+ npm run prepare
57
+ ```
58
+
59
+ This will:
60
+ 1. Build the React app
61
+ 2. Compile TypeScript
62
+ 3. Copy assets to the dist directory
63
+
64
+ The built plugin will serve the React app statically in production.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var express_1 = __importDefault(require("express"));
7
+ var path_1 = __importDefault(require("path"));
8
+ var fs_1 = __importDefault(require("fs"));
9
+ var app = (0, express_1.default)();
10
+ var port = 3001;
11
+ // Configure development options
12
+ var options = {
13
+ apiPath: "/docs/api"
14
+ };
15
+ // Load sample data
16
+ var sampleData;
17
+ try {
18
+ sampleData = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, "../sample.json"), "utf-8"));
19
+ console.log("Sample data loaded successfully");
20
+ }
21
+ catch (error) {
22
+ console.error("Failed to load sample.json:", error);
23
+ process.exit(1);
24
+ }
25
+ // Enable CORS for development
26
+ app.use(function (req, res, next) {
27
+ res.header("Access-Control-Allow-Origin", "*");
28
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
29
+ next();
30
+ });
31
+ // Serve API endpoint
32
+ app.get(options.apiPath, function (req, res) {
33
+ console.log("API request received at ".concat(options.apiPath));
34
+ res.json({
35
+ routes: Array.isArray(sampleData) ? sampleData : [sampleData]
36
+ });
37
+ });
38
+ // Start the server
39
+ app.listen(port, function () {
40
+ console.log("Mock API server running at http://localhost:".concat(port));
41
+ console.log("API endpoint available at http://localhost:".concat(port).concat(options.apiPath));
42
+ console.log("\nTo develop the React app:");
43
+ console.log("1. Keep this server running");
44
+ console.log("2. In another terminal, cd to react-app and run 'npm run dev'");
45
+ console.log("3. Open http://localhost:5173 to see the app");
46
+ });
package/dist/index.d.ts CHANGED
@@ -2,8 +2,14 @@ import { FlinkPlugin } from "@flink-app/flink";
2
2
  export type ApiDocOptions = {
3
3
  /**
4
4
  * Path to where api docs can be accessed
5
+ * Defaults to "/docs"
5
6
  */
6
7
  path?: string;
8
+ /**
9
+ * Path to where route and schemas are fetched from.
10
+ * Defaults to "/docs/api"
11
+ */
12
+ apiPath?: string;
7
13
  /**
8
14
  * Name of plugin
9
15
  */
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.apiDocPlugin = void 0;
4
- var path_1 = require("path");
7
+ var path_1 = __importDefault(require("path"));
8
+ var express_1 = __importDefault(require("express"));
5
9
  var apiDocPlugin = function (options) {
6
10
  if (options === void 0) { options = {}; }
7
11
  return {
@@ -11,23 +15,15 @@ var apiDocPlugin = function (options) {
11
15
  };
12
16
  exports.apiDocPlugin = apiDocPlugin;
13
17
  function init(app, options) {
14
- // TODO: Use parsedHandlerConfig
15
18
  var expressApp = app.expressApp, handlers = app.handlers;
16
19
  if (!expressApp) {
17
20
  // should not happen
18
21
  throw new Error("Express app not initialized");
19
22
  }
20
- // NOTE: This will probably break if any other plugin "claims" pug as view engine
21
- expressApp.engine("pug", require("pug").__express);
22
- expressApp.set("views", (0, path_1.join)(__dirname, "views"));
23
- expressApp.set("view engine", "pug");
24
- expressApp === null || expressApp === void 0 ? void 0 : expressApp.get(options.path || "/docs", function (req, res) {
25
- var sortedHandlers = handlers.sort(function (routeA, routeB) {
26
- return routeA.routeProps.path.localeCompare(routeB.routeProps.path);
27
- });
28
- res.render("index", {
29
- title: options.title || "API Docs",
30
- handlers: sortedHandlers,
31
- });
23
+ var staticPath = path_1.default.resolve(__dirname, "react-app");
24
+ expressApp === null || expressApp === void 0 ? void 0 : expressApp.use(options.path || "/docs", express_1.default.static(staticPath));
25
+ expressApp === null || expressApp === void 0 ? void 0 : expressApp.get(options.apiPath || "/docs/api", function (req, res) {
26
+ var sortedHandlers = handlers.sort(function (routeA, routeB) { return routeA.routeProps.path.localeCompare(routeB.routeProps.path); });
27
+ res.json({ routes: sortedHandlers });
32
28
  });
33
29
  }
@@ -0,0 +1,3 @@
1
+ import { FlinkApp } from "@flink-app/flink";
2
+ import type { Request, Response } from "express";
3
+ export declare function createMcpHandler(app: FlinkApp<any>): (req: Request, res: Response) => Promise<void>;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createMcpHandler = void 0;
40
+ // Helper function to simplify schema conversion
41
+ function flattenProperties(properties, required, parent) {
42
+ if (required === void 0) { required = []; }
43
+ if (parent === void 0) { parent = ""; }
44
+ var result = [];
45
+ for (var _i = 0, _a = Object.entries(properties || {}); _i < _a.length; _i++) {
46
+ var _b = _a[_i], key = _b[0], value = _b[1];
47
+ var path = parent ? "".concat(parent, ".").concat(key) : key;
48
+ result.push({
49
+ name: path,
50
+ type: value.type,
51
+ description: value.description,
52
+ required: required.includes(key),
53
+ });
54
+ if (value.type === "object" && value.properties) {
55
+ result = result.concat(flattenProperties(value.properties, value.required || [], path));
56
+ }
57
+ if (value.type === "array" && value.items && value.items.type === "object" && value.items.properties) {
58
+ result = result.concat(flattenProperties(value.items.properties, value.items.required || [], "".concat(path, "[]")));
59
+ }
60
+ }
61
+ return result;
62
+ }
63
+ // For creating an Express endpoint that handles MCP requests
64
+ function createMcpHandler(app) {
65
+ var _this = this;
66
+ return function (req, res) { return __awaiter(_this, void 0, void 0, function () {
67
+ var endpoints, _a, method, params, tools, _b, name_1, args, endpoint, response_1;
68
+ return __generator(this, function (_c) {
69
+ try {
70
+ endpoints = app.handlers.map(function (handler) {
71
+ var _a, _b, _c, _d, _e, _f;
72
+ return ({
73
+ path: handler.routeProps.path,
74
+ method: handler.routeProps.method.toUpperCase(),
75
+ description: (_b = (_a = handler.schema) === null || _a === void 0 ? void 0 : _a.reqSchema) === null || _b === void 0 ? void 0 : _b.description,
76
+ queryParams: handler.queryMetadata,
77
+ pathParams: handler.paramsMetadata,
78
+ requestBody: ((_d = (_c = handler.schema) === null || _c === void 0 ? void 0 : _c.reqSchema) === null || _d === void 0 ? void 0 : _d.properties)
79
+ ? flattenProperties(handler.schema.reqSchema.properties, handler.schema.reqSchema.required || [])
80
+ : undefined,
81
+ responseBody: ((_f = (_e = handler.schema) === null || _e === void 0 ? void 0 : _e.resSchema) === null || _f === void 0 ? void 0 : _f.properties)
82
+ ? flattenProperties(handler.schema.resSchema.properties, handler.schema.resSchema.required || [])
83
+ : undefined,
84
+ });
85
+ });
86
+ // For WebSocket upgrade requests
87
+ if (req.headers.upgrade === "websocket") {
88
+ // Handle WebSocket upgrade (requires additional WebSocket setup)
89
+ res.status(426).send("WebSocket support not yet implemented");
90
+ return [2 /*return*/];
91
+ }
92
+ // For regular HTTP requests to the MCP endpoint
93
+ if (req.method === "GET") {
94
+ res.json({
95
+ name: "api-docs-mcp-server",
96
+ version: "1.0.0",
97
+ description: "MCP server providing API documentation tools",
98
+ tools: endpoints.length,
99
+ endpoints: endpoints.map(function (e) { return ({
100
+ path: e.path,
101
+ method: e.method,
102
+ description: e.description,
103
+ }); }),
104
+ });
105
+ return [2 /*return*/];
106
+ }
107
+ // Handle MCP protocol messages over HTTP POST
108
+ if (req.method === "POST") {
109
+ _a = req.body, method = _a.method, params = _a.params;
110
+ // Handle tools/list
111
+ if (method === "tools/list") {
112
+ tools = endpoints.map(function (endpoint) { return ({
113
+ name: "".concat(endpoint.method.toLowerCase(), "_").concat(endpoint.path.replace(/[/:]/g, "_")),
114
+ description: endpoint.description || "".concat(endpoint.method, " ").concat(endpoint.path),
115
+ inputSchema: generateInputSchema(endpoint),
116
+ }); });
117
+ res.json({ tools: tools });
118
+ return [2 /*return*/];
119
+ }
120
+ // Handle tools/call
121
+ if (method === "tools/call") {
122
+ _b = params || {}, name_1 = _b.name, args = _b.arguments;
123
+ endpoint = endpoints.find(function (e) { return "".concat(e.method.toLowerCase(), "_").concat(e.path.replace(/[/:]/g, "_")) === name_1; });
124
+ if (!endpoint) {
125
+ res.json({
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: "Tool not found: ".concat(name_1),
130
+ },
131
+ ],
132
+ });
133
+ return [2 /*return*/];
134
+ }
135
+ response_1 = "Endpoint Details:\n";
136
+ response_1 += "Path: ".concat(endpoint.path, "\n");
137
+ response_1 += "Method: ".concat(endpoint.method, "\n");
138
+ if (endpoint.description) {
139
+ response_1 += "Description: ".concat(endpoint.description, "\n");
140
+ }
141
+ if (endpoint.pathParams && endpoint.pathParams.length > 0) {
142
+ response_1 += "\nPath Parameters:\n";
143
+ endpoint.pathParams.forEach(function (param) {
144
+ response_1 += "- ".concat(param.name, ": ").concat(param.description || "No description", "\n");
145
+ });
146
+ }
147
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
148
+ response_1 += "\nQuery Parameters:\n";
149
+ endpoint.queryParams.forEach(function (param) {
150
+ response_1 += "- ".concat(param.name).concat(param.required ? " (required)" : "", ": ").concat(param.type || "string", " - ").concat(param.description || "No description", "\n");
151
+ });
152
+ }
153
+ if (endpoint.requestBody && endpoint.requestBody.length > 0) {
154
+ response_1 += "\nRequest Body Schema:\n";
155
+ response_1 += JSON.stringify(endpoint.requestBody, null, 2);
156
+ }
157
+ if (endpoint.responseBody && endpoint.responseBody.length > 0) {
158
+ response_1 += "\nResponse Body Schema:\n";
159
+ response_1 += JSON.stringify(endpoint.responseBody, null, 2);
160
+ }
161
+ res.json({
162
+ content: [
163
+ {
164
+ type: "text",
165
+ text: response_1,
166
+ },
167
+ ],
168
+ });
169
+ return [2 /*return*/];
170
+ }
171
+ res.status(400).json({
172
+ error: "Unknown method",
173
+ details: "Method ".concat(method, " is not supported"),
174
+ });
175
+ return [2 /*return*/];
176
+ }
177
+ res.status(405).send("Method not allowed");
178
+ }
179
+ catch (error) {
180
+ console.error("MCP handler error:", error);
181
+ res.status(500).send("Internal server error");
182
+ }
183
+ return [2 /*return*/];
184
+ });
185
+ }); };
186
+ }
187
+ exports.createMcpHandler = createMcpHandler;
188
+ function generateInputSchema(endpoint) {
189
+ var inputSchema = {
190
+ type: "object",
191
+ properties: {},
192
+ required: [],
193
+ };
194
+ // Add path parameters
195
+ if (endpoint.pathParams && endpoint.pathParams.length > 0) {
196
+ endpoint.pathParams.forEach(function (param) {
197
+ inputSchema.properties[param.name] = {
198
+ type: "string",
199
+ description: param.description || "Path parameter: ".concat(param.name),
200
+ };
201
+ inputSchema.required.push(param.name);
202
+ });
203
+ }
204
+ // Add query parameters
205
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
206
+ endpoint.queryParams.forEach(function (param) {
207
+ inputSchema.properties[param.name] = {
208
+ type: param.type || "string",
209
+ description: param.description || "Query parameter: ".concat(param.name),
210
+ };
211
+ if (param.required) {
212
+ inputSchema.required.push(param.name);
213
+ }
214
+ });
215
+ }
216
+ // Add request body
217
+ if (endpoint.requestBody && endpoint.requestBody.length > 0) {
218
+ var requestBodySchema_1 = {
219
+ type: "object",
220
+ properties: {},
221
+ required: [],
222
+ };
223
+ // Convert flat properties back to nested structure for schema
224
+ endpoint.requestBody.forEach(function (prop) {
225
+ var parts = prop.name.split(".");
226
+ var current = requestBodySchema_1.properties;
227
+ for (var i = 0; i < parts.length - 1; i++) {
228
+ var part = parts[i];
229
+ if (!current[part]) {
230
+ current[part] = {
231
+ type: "object",
232
+ properties: {},
233
+ };
234
+ }
235
+ current = current[part].properties;
236
+ }
237
+ var lastPart = parts[parts.length - 1];
238
+ current[lastPart] = {
239
+ type: prop.type,
240
+ description: prop.description,
241
+ };
242
+ if (prop.required) {
243
+ requestBodySchema_1.required.push(parts[0]);
244
+ }
245
+ });
246
+ inputSchema.properties.body = requestBodySchema_1;
247
+ inputSchema.required.push("body");
248
+ }
249
+ return inputSchema;
250
+ }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
3
+ <title>API Docs Favicon</title>
4
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <!-- Document outline -->
6
+ <path d="M6,2 L20,2 L26,8 L26,28 L6,28 Z" fill="#FFFFFF" stroke="#2563EB" stroke-width="2" />
7
+ <!-- Folded corner -->
8
+ <path d="M20,2 L20,8 L26,8 Z" fill="#E5E7EB" stroke="#2563EB" stroke-width="2" />
9
+ <!-- Document lines -->
10
+ <line x1="10" y1="14" x2="22" y2="14" stroke="#2563EB" stroke-width="2" />
11
+ <line x1="10" y1="18" x2="22" y2="18" stroke="#2563EB" stroke-width="2" />
12
+ <line x1="10" y1="22" x2="18" y2="22" stroke="#2563EB" stroke-width="2" />
13
+ </g>
14
+ </svg>