@meloqa/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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 meloQA contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # meloqa-mcp
2
+
3
+ Model Context Protocol (MCP) server for the meloQA public **v1** API.
4
+
5
+ Exposes every operation from the v1 OpenAPI spec as an MCP tool, so LLM clients (Claude Desktop, Claude Code, Cursor, etc.) can read and manage meloQA projects, test cases, cycles, executions, bugs, links, and reference data.
6
+
7
+ > **Status:** pre-release. Defaults to the public endpoint `https://api.meloqa.com`; override `MELOQA_BASE_URL` to point at another instance (e.g. a local `http://localhost:3000`).
8
+
9
+ ## Requirements
10
+
11
+ - Node.js **18+** (uses native `fetch`).
12
+ - A meloQA **API token** (see User Menu → API Tokens in the meloQA app).
13
+
14
+ ## Configure in your MCP client
15
+
16
+ ### Claude Desktop / Claude Code
17
+
18
+ Add an entry to your MCP config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS for Claude Desktop, or `.claude.json` for Claude Code):
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "meloqa": {
24
+ "command": "npx",
25
+ "args": ["-y", "@meloqa/mcp-server"],
26
+ "env": {
27
+ "MELOQA_API_TOKEN": "your-token-here",
28
+ "MELOQA_BASE_URL": "https://api.meloqa.com"
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Restart the client. The `meloqa` server should appear with ~70 tools (one per v1 endpoint).
36
+
37
+ ## Environment variables
38
+
39
+ | Variable | Required | Default | Description |
40
+ | --- | --- | --- | --- |
41
+ | `MELOQA_API_TOKEN` | yes | — | API token sent as `Authorization: Bearer <token>`. |
42
+ | `MELOQA_BASE_URL` | no | `https://api.meloqa.com` | Base URL of the meloQA instance. Override to point at another deployment (e.g. `http://localhost:3000`). |
43
+ | `MELOQA_RATE_LIMIT` | no | `30` | Client-side limit (requests/minute) used to throttle outgoing calls. Match the server's limit. |
44
+
45
+ ## Token lifecycle
46
+
47
+ The token sits in plaintext inside your MCP client config (e.g. `~/.claude.json`). The file is `chmod 600` by default, but the token still lives on disk — treat it as a credential.
48
+
49
+ **To rotate** (recommended periodically, and immediately if you suspect exposure):
50
+
51
+ 1. Open the meloQA UI → **User Menu → API Tokens**.
52
+ 2. Click **Revoke** on the current token. It stops working server-side within seconds.
53
+ 3. Click **Generate new token** and copy the new value.
54
+ 4. Replace `MELOQA_API_TOKEN` in your MCP client config with the new value.
55
+ 5. Restart the MCP client (Claude Desktop / Claude Code / etc.) so it re-spawns the server with the updated env.
56
+
57
+ **Per-machine tokens**: generate a separate token for each machine/environment so revoking one doesn't break the others.
58
+
59
+ **Do not commit** your MCP client config (`~/.claude.json`, `.mcp.json`, `claude_desktop_config.json`) to a public repo — it contains the token.
60
+
61
+ ## Tools
62
+
63
+ Tool names follow `<resource>_<verb>`, derived from the OpenAPI spec:
64
+
65
+ - `<resource>_list`, `<resource>_get`, `<resource>_create`, `<resource>_update`, `<resource>_delete` for CRUD endpoints.
66
+ - Action endpoints keep the action as the suffix: `executions_run`, `executions_pause`, `executions_finish`, `test_cases_unarchive`, `links_batch_create`.
67
+ - Sub-resource listings use the parent resource as the prefix: `projects_members_list`, `projects_execution_statuses_list`, etc.
68
+
69
+ Each tool's `description` includes the OpenAPI `summary`, the tag, and any validation rules documented on the endpoint (e.g. max lengths, required referenced IDs).
70
+
71
+ ## Behavior notes
72
+
73
+ - **Auth header**: the token is sent as `Authorization: Bearer <token>` (RFC 6750).
74
+ - **Rate limit**: the client uses an in-process sliding-window limiter (default 30 req/min). It blocks outgoing calls until a slot is available. If you hit the server-side 429 anyway, the tool call surfaces a clear error.
75
+ - **Errors**: non-2xx responses (400/401/403/404/etc.) are returned as MCP tool errors with the response body included.
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ npm install
81
+ npm run sync-spec # refresh spec/openapi.json from MELOQA_SPEC_URL (default http://localhost:3000/v1/docs.json)
82
+ npm run gen # regenerate src/tools.generated.ts from the spec
83
+ npm run build # gen + tsc → dist/
84
+ npm run dev # run from source via tsx
85
+ ```
86
+
87
+ To test the generated server against a running meloQA instance:
88
+
89
+ ```bash
90
+ MELOQA_API_TOKEN=... MELOQA_BASE_URL=https://api.meloqa.com npm run dev
91
+ ```
92
+
93
+ Then pipe JSON-RPC messages on stdin or wire it up via an MCP client.
94
+
95
+ ## License
96
+
97
+ MIT
package/dist/client.js ADDED
@@ -0,0 +1,89 @@
1
+ export class MeloqaError extends Error {
2
+ status;
3
+ bodyText;
4
+ bodyJson;
5
+ constructor(status, bodyText, bodyJson) {
6
+ super(`meloQA API ${status}: ${typeof bodyJson === "object" && bodyJson && "message" in bodyJson ? bodyJson.message : bodyText.slice(0, 200)}`);
7
+ this.status = status;
8
+ this.bodyText = bodyText;
9
+ this.bodyJson = bodyJson;
10
+ this.name = "MeloqaError";
11
+ }
12
+ }
13
+ /**
14
+ * Sliding-window rate limiter: tracks request timestamps within the last 60s and
15
+ * sleeps the caller until a slot frees up. Single-process; sufficient for a single
16
+ * MCP server instance serving one user.
17
+ */
18
+ class RateLimiter {
19
+ limit;
20
+ windowMs;
21
+ timestamps = [];
22
+ constructor(limit, windowMs = 60_000) {
23
+ this.limit = limit;
24
+ this.windowMs = windowMs;
25
+ }
26
+ async acquire() {
27
+ while (true) {
28
+ const now = Date.now();
29
+ this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
30
+ if (this.timestamps.length < this.limit) {
31
+ this.timestamps.push(now);
32
+ return;
33
+ }
34
+ const waitMs = this.windowMs - (now - this.timestamps[0]) + 5;
35
+ await new Promise((r) => setTimeout(r, waitMs));
36
+ }
37
+ }
38
+ }
39
+ export class MeloqaClient {
40
+ baseUrl;
41
+ apiToken;
42
+ limiter;
43
+ constructor(cfg) {
44
+ this.baseUrl = cfg.baseUrl.replace(/\/$/, "");
45
+ this.apiToken = cfg.apiToken;
46
+ this.limiter = new RateLimiter(cfg.rateLimitPerMin ?? 30);
47
+ }
48
+ async send(req) {
49
+ await this.limiter.acquire();
50
+ const url = new URL(this.baseUrl + req.path);
51
+ if (req.query) {
52
+ for (const [k, v] of Object.entries(req.query)) {
53
+ url.searchParams.set(k, v);
54
+ }
55
+ }
56
+ const init = {
57
+ method: req.method,
58
+ headers: {
59
+ Authorization: `Bearer ${this.apiToken}`,
60
+ Accept: "application/json",
61
+ },
62
+ };
63
+ if (req.body !== undefined) {
64
+ init.headers["Content-Type"] = "application/json";
65
+ init.body = JSON.stringify(req.body);
66
+ }
67
+ const res = await fetch(url, init);
68
+ const text = await res.text();
69
+ let json = undefined;
70
+ if (text) {
71
+ try {
72
+ json = JSON.parse(text);
73
+ }
74
+ catch {
75
+ json = undefined;
76
+ }
77
+ }
78
+ if (!res.ok) {
79
+ if (res.status === 429) {
80
+ throw new MeloqaError(429, text, {
81
+ message: "Rate limit exceeded (meloQA v1 caps at 30 req/min). Slow down and retry.",
82
+ });
83
+ }
84
+ throw new MeloqaError(res.status, text, json);
85
+ }
86
+ return json ?? null;
87
+ }
88
+ }
89
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ function getRequiredEnv(name) {
5
+ const value = process.env[name];
6
+ if (!value) {
7
+ console.error(`Missing required environment variable: ${name}`);
8
+ process.exit(1);
9
+ }
10
+ return value;
11
+ }
12
+ const baseUrl = process.env.MELOQA_BASE_URL ?? "https://api.meloqa.com";
13
+ const apiToken = getRequiredEnv("MELOQA_API_TOKEN");
14
+ const rateLimitPerMin = process.env.MELOQA_RATE_LIMIT
15
+ ? Number(process.env.MELOQA_RATE_LIMIT)
16
+ : undefined;
17
+ const server = createServer({ baseUrl, apiToken, rateLimitPerMin });
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,wBAAwB,CAAC;AACxE,MAAM,QAAQ,GAAG,cAAc,CAAC,kBAAkB,CAAC,CAAC;AACpD,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB;IACnD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACvC,CAAC,CAAC,SAAS,CAAC;AAEd,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;AACpE,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAE7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,62 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { MeloqaClient, MeloqaError } from "./client.js";
4
+ import { tools } from "./tools.generated.js";
5
+ export function createServer(opts) {
6
+ const client = new MeloqaClient(opts);
7
+ const toolsByName = new Map(tools.map((t) => [t.name, t]));
8
+ const server = new Server({
9
+ name: "meloqa-mcp",
10
+ version: "0.1.0",
11
+ }, {
12
+ capabilities: {
13
+ tools: {},
14
+ },
15
+ });
16
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
17
+ tools: tools.map((t) => ({
18
+ name: t.name,
19
+ description: t.description,
20
+ inputSchema: t.inputSchema,
21
+ })),
22
+ }));
23
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
24
+ const tool = toolsByName.get(req.params.name);
25
+ if (!tool) {
26
+ return {
27
+ isError: true,
28
+ content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }],
29
+ };
30
+ }
31
+ const input = (req.params.arguments ?? {});
32
+ try {
33
+ const httpReq = tool.request(input);
34
+ const result = await client.send(httpReq);
35
+ return {
36
+ content: [
37
+ { type: "text", text: JSON.stringify(result, null, 2) },
38
+ ],
39
+ };
40
+ }
41
+ catch (err) {
42
+ if (err instanceof MeloqaError) {
43
+ return {
44
+ isError: true,
45
+ content: [
46
+ {
47
+ type: "text",
48
+ text: `meloQA API error ${err.status}: ${err.message}\n\n${err.bodyText.slice(0, 1000)}`,
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ return {
55
+ isError: true,
56
+ content: [{ type: "text", text: `Request failed: ${msg}` }],
57
+ };
58
+ }
59
+ });
60
+ return server;
61
+ }
62
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}