|
1 | 1 | # Contributing |
2 | 2 |
|
3 | | -Thanks for helping improve `parseable-mcp-server`. This document covers the design principles, layout, and the practical mechanics of adding a tool or fixing a bug. |
4 | | - |
5 | | -## Design principles |
6 | | - |
7 | | -1. **Auth is the trust boundary.** If a user supplies credentials, they trust the MCP client. No env-var write-gates - MCP clients already show per-call approval UI. Re-introduce gates only for hosted/multi-tenant variants. |
8 | | -2. **Time-bounded queries are mandatory.** Never unbounded scans. Default window 60 minutes, max 1440. |
9 | | -3. **Row caps are enforced.** Default 100, hard max via `PARSEABLE_MAX_ROWS` (default 1000). |
10 | | -4. **One tool, one verb.** No mega-tools. |
11 | | -5. **Tool descriptions are user-facing.** They tell the calling client when to use the tool, not just what. |
12 | | -6. **No `ingest_event` tool.** Wrong shape, encourages garbage data - use Parseable's ingest API directly. |
13 | | -7. **No destructive tools** (`delete_*`). Force the Parseable UI for safety. Destructiveness is asymmetric. |
14 | | -8. **Loud descriptions on side-effecting tools.** `evaluate_alert` warns "MAY TRIGGER REAL NOTIFICATIONS" so callers confirm with the user first. |
15 | | -9. **Cross-platform first.** Tools-only over stdio is universal. Optional MCP features (elicitation, prompts, resources) only with a chat-Q&A fallback that works everywhere. |
16 | | - |
17 | | -## Parseable API gotchas |
18 | | - |
19 | | -Reference docs: |
20 | | -- [API Reference](https://www.parseable.com/docs/api) |
21 | | -- [Alerting](https://www.parseable.com/docs/user-guide/alerting) |
22 | | -- [RBAC](https://www.parseable.com/docs/user-guide/rbac) |
23 | | -- [PromQL](https://www.parseable.com/docs/user-guide/promql) |
24 | | -- [Retention](https://www.parseable.com/docs/user-guide/retention) |
25 | | -- [Installation modes (self-hosted)](https://www.parseable.com/docs/self-hosted/installation) |
26 | | -- [SQL Editor](https://www.parseable.com/docs/user-guide/sql-editor) |
27 | | - |
28 | | -Things we learned the hard way (not always obvious from docs): |
29 | | -- Query payload uses **camelCase** keys. Required: `query`, `startTime`, `endTime`. Optional: `sendNull`. |
30 | | -- Auth is **Basic** (username:password base64). Switch to PAT when Parseable ships it server-side. |
31 | | -- All paths prefixed `/api/v1` - except PromQL. |
32 | | -- **PromQL endpoints live under `/prometheus/api/v1/`** (different base). `start`/`end`/`time` params must be **unix epoch seconds** in practice - `query_promql` auto-converts RFC3339 → epoch before sending. |
33 | | -- Cluster endpoints (`/cluster/info`, `/cluster/metrics`) only exist on **distributed** Parseable. Standalone returns 404. The `classifyStatus` helper surfaces a clear hint. |
34 | | - |
35 | | -## File layout |
| 3 | +`parseable-mcp-server` is a thin MCP-to-Parseable adapter. Parseable owns tool |
| 4 | +definitions, validation, RBAC, execution, and edition-specific capabilities. |
| 5 | +This repository must not duplicate tool implementations. |
36 | 6 |
|
37 | | -``` |
38 | | -src/ |
39 | | -├── server.ts # MCP wire-up, stdio transport, tool registration loop |
40 | | -├── config.ts # env loader |
41 | | -├── client.ts # Parseable HTTP client (Basic auth, AbortController timeout, error classification) |
42 | | -└── tools/ |
43 | | - ├── types.ts # ToolDef interface + jsonResult/errorResult helpers |
44 | | - ├── index.ts # tool registry - add new tools here |
45 | | - └── <tool_name>.ts # one file per tool |
46 | | -test/ |
47 | | -├── client.test.ts # parseErrorBody, classifyStatus, request behavior |
48 | | -├── client_methods.test.ts # parametrized: every client method's verb + URL + body |
49 | | -├── tools.test.ts # logic-heavy tools (query_sql, query_promql, explain_query) |
50 | | -├── tools_passthrough.test.ts # every pass-through tool delegates correctly |
51 | | -├── registry.test.ts # meta: unique names, snake_case, no destructive tools |
52 | | -├── config.test.ts # env vars, defaults, overrides |
53 | | -└── types.test.ts # jsonResult / errorResult helpers |
54 | | -``` |
55 | | - |
56 | | -## Adding a tool |
57 | | - |
58 | | -1. Create `src/tools/<name>.ts` exporting a `ToolDef`. |
59 | | -2. Append to the array in `src/tools/index.ts`. The server auto-registers everything in that array. |
60 | | -3. Add a HTTP method to `src/client.ts` if a new endpoint is involved. |
61 | | -4. Add a test entry to `test/client_methods.test.ts` (verb + URL). |
62 | | -5. Add a pass-through test to `test/tools_passthrough.test.ts` for boring tools, or a logic test in `test/tools.test.ts` for tools with their own behavior. |
63 | | -6. `npm run lint && npm test && npm run build`. |
64 | | - |
65 | | -Tool description guidelines: |
66 | | -- Tell the caller *when* to use the tool, not just what. |
67 | | -- Mention prerequisite tools (e.g. "Use `list_datasets` first to discover names"). |
68 | | -- Loud-warn on side effects: "MAY FIRE REAL NOTIFICATIONS", "Destructive - confirm first". |
69 | | -- Keep description under ~500 chars unless steering a multi-step flow. |
70 | | - |
71 | | -## TypeScript gotcha |
| 7 | +## Architecture |
72 | 8 |
|
73 | | -`McpServer.registerTool` has deeply nested generic inference (`OutputArgs`/`InputArgs` union with `AnySchema`). Calling it inside a `for (const tool of tools)` loop triggers `TS2589: Type instantiation is excessively deep`. Workaround in `src/server.ts`: cast to a simpler function signature before invocation. Don't unwind into per-file registrations - the loop is fine, the cast is the right fix. |
| 9 | +```text |
| 10 | +MCP tools/list |
| 11 | + -> GET /api/prism/v1/llm/tools/list |
74 | 12 |
|
75 | | -## Lint + format - Biome |
76 | | - |
77 | | -Single tool replaces ESLint + Prettier. Config in `biome.json`. |
78 | | - |
79 | | -- 2-space indent, 100-char line width |
80 | | -- Double quotes, semicolons, trailing commas |
81 | | -- Imports auto-organized on `npm run fix` |
82 | | -- `useImportType` (warn) - prefer `import type` for types |
83 | | -- `useNodejsImportProtocol` (warn) - `node:fs` not `fs` |
84 | | -- `noNonNullAssertion` (warn) - prefer `if (x)` narrowing over `x!` |
85 | | -- `noExplicitAny` (warn) |
| 13 | +MCP tools/call |
| 14 | + -> POST /api/prism/v1/llm/tools/call |
| 15 | +``` |
86 | 16 |
|
87 | | -Commands: |
88 | | -- `npm run lint` - check only (CI uses this) |
89 | | -- `npm run fix` - apply all safe auto-fixes (imports, formatting, lint rules) |
90 | | -- `npm run format` - formatting only |
| 17 | +Registry responses are validated, cached per Parseable URL, tenant, and |
| 18 | +credential scope, then revalidated with `ETag`. Invocation results must be a |
| 19 | +valid MCP `CallToolResult` and are never cached. |
91 | 20 |
|
92 | | -Biome runs in ~10ms across the whole repo. |
| 21 | +Key files: |
93 | 22 |
|
94 | | -## Testing |
| 23 | +```text |
| 24 | +src/bootstrap.ts MCP tools/list and tools/call handlers |
| 25 | +src/client.ts Parseable registry and invocation HTTP client |
| 26 | +src/remote-tools.ts registry validation, LRU/TTL cache, ETag state |
| 27 | +src/http.ts stateless Streamable HTTP transport and request auth |
| 28 | +src/stdio.ts stdio transport |
| 29 | +src/cloud.ts Parseable Cloud API-key routing |
| 30 | +``` |
95 | 31 |
|
96 | | -Vitest + native `fetch` mocking. Tests live in `test/*.test.ts`. Coverage via `npm run test:coverage` (v8 provider). Excluded: `src/server.ts` (boot wiring), `src/tools/index.ts` (registry - covered by `registry.test.ts`). |
| 32 | +## Adding or changing tools |
97 | 33 |
|
98 | | -Current: **95% statements / 98% functions / 95% lines / 108 tests**. |
| 34 | +Tool changes belong in the Parseable server catalog and executor. This adapter |
| 35 | +should change only when the REST or MCP contract changes. |
99 | 36 |
|
100 | | -## CI |
| 37 | +Required invariants: |
101 | 38 |
|
102 | | -`.github/workflows/ci.yml` runs on push + PR to main: `npm ci` → `npm run lint` → `npm run build:all` → `npm test`. Node 22. |
| 39 | +- Forward caller authentication and tenant routing on every upstream request. |
| 40 | +- Reject malformed registries, invalid schemas, and duplicate tool names. |
| 41 | +- Never cache tool invocation results. |
| 42 | +- Scope registry cache entries so credentials cannot share private catalogs. |
| 43 | +- Forward structured Parseable tool failures as MCP tool results. |
| 44 | +- Convert malformed upstream results and transport failures into MCP errors. |
| 45 | +- Do not log API keys, tool arguments, or tool results. |
103 | 46 |
|
104 | | -## Deliberately excluded - won't be accepted |
| 47 | +## Verification |
105 | 48 |
|
106 | | -- `ingest_event` - wrong shape for this protocol |
107 | | -- `delete_*` - destructive, UI only |
108 | | -- `create_user` / `create_role` / RBAC mutation - UI/CLI only |
109 | | -- Live tail / streaming subscriptions - stdio transport not the right fit; use Parseable UI |
| 49 | +```bash |
| 50 | +npm run lint |
| 51 | +npm run build:all |
| 52 | +npm test |
| 53 | +``` |
110 | 54 |
|
111 | | -## Releasing |
| 55 | +Tests use Vitest, native `fetch` mocks, and linked in-memory MCP transports. |
| 56 | +Add coverage for both REST mapping and complete MCP discovery/invocation flows. |
112 | 57 |
|
113 | | -Publishing a GitHub Release triggers `.github/workflows/release.yml`. The workflow verifies that the release tag matches the version in `package.json`, runs lint + build + test, then publishes the package to npm through trusted publishing with automatic provenance. |
| 58 | +## Style and release |
114 | 59 |
|
115 | | -Before the first release, configure a GitHub Actions trusted publisher in the package settings on npmjs.com. Use organization `parseablehq`, repository `parseable-mcp-server`, workflow `release.yml`, and allow `npm publish`. No npm token is required. Create releases with a `v`-prefixed tag matching `package.json` (for example, package version `0.3.0` requires tag `v0.3.0`). |
| 60 | +Biome owns formatting and linting (`npm run fix`). GitHub releases trigger npm |
| 61 | +publishing; release tag and `package.json` version must match. |
0 commit comments