@atoms-tech/atoms-mcp 0.3.0 → 0.3.1

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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.js +1 -90
  4. package/package.json +12 -3
  5. package/dist/apps/register.d.ts +0 -36
  6. package/dist/apps/register.js +0 -65
  7. package/dist/auth/login.d.ts +0 -24
  8. package/dist/auth/login.js +0 -413
  9. package/dist/auth/refresh.d.ts +0 -16
  10. package/dist/auth/refresh.js +0 -81
  11. package/dist/auth/token-store.d.ts +0 -33
  12. package/dist/auth/token-store.js +0 -60
  13. package/dist/config.d.ts +0 -18
  14. package/dist/config.js +0 -18
  15. package/dist/db/client.d.ts +0 -29
  16. package/dist/db/client.js +0 -109
  17. package/dist/db/graph.d.ts +0 -7
  18. package/dist/db/graph.js +0 -7
  19. package/dist/db/queries.d.ts +0 -76
  20. package/dist/db/queries.js +0 -209
  21. package/dist/index.d.ts +0 -11
  22. package/dist/middleware/audit.d.ts +0 -25
  23. package/dist/middleware/audit.js +0 -43
  24. package/dist/middleware/rate-limiter.d.ts +0 -20
  25. package/dist/middleware/rate-limiter.js +0 -42
  26. package/dist/middleware/validator.d.ts +0 -21
  27. package/dist/middleware/validator.js +0 -90
  28. package/dist/server.d.ts +0 -14
  29. package/dist/server.js +0 -679
  30. package/dist/tools/_base.d.ts +0 -57
  31. package/dist/tools/_base.js +0 -108
  32. package/dist/tools/bulk-import.d.ts +0 -69
  33. package/dist/tools/bulk-import.js +0 -187
  34. package/dist/tools/create-item.d.ts +0 -42
  35. package/dist/tools/create-item.js +0 -117
  36. package/dist/tools/delete-item.d.ts +0 -37
  37. package/dist/tools/delete-item.js +0 -68
  38. package/dist/tools/export-mermaid.d.ts +0 -35
  39. package/dist/tools/export-mermaid.js +0 -124
  40. package/dist/tools/get-coverage.d.ts +0 -33
  41. package/dist/tools/get-coverage.js +0 -35
  42. package/dist/tools/get-history.d.ts +0 -33
  43. package/dist/tools/get-history.js +0 -52
  44. package/dist/tools/get-item.d.ts +0 -61
  45. package/dist/tools/get-item.js +0 -92
  46. package/dist/tools/link-items.d.ts +0 -40
  47. package/dist/tools/link-items.js +0 -149
  48. package/dist/tools/list-items.d.ts +0 -36
  49. package/dist/tools/list-items.js +0 -35
  50. package/dist/tools/list-projects.d.ts +0 -37
  51. package/dist/tools/list-projects.js +0 -27
  52. package/dist/tools/project-summary.d.ts +0 -63
  53. package/dist/tools/project-summary.js +0 -169
  54. package/dist/tools/record-test-result.d.ts +0 -40
  55. package/dist/tools/record-test-result.js +0 -79
  56. package/dist/tools/search.d.ts +0 -33
  57. package/dist/tools/search.js +0 -27
  58. package/dist/tools/trace.d.ts +0 -52
  59. package/dist/tools/trace.js +0 -165
  60. package/dist/tools/update-item.d.ts +0 -42
  61. package/dist/tools/update-item.js +0 -97
  62. package/dist/types/responses.d.ts +0 -57
  63. package/dist/types/responses.js +0 -5
  64. package/dist/types/work-item.d.ts +0 -68
  65. package/dist/types/work-item.js +0 -7
package/dist/db/client.js DELETED
@@ -1,109 +0,0 @@
1
- /**
2
- * Supabase client factory for the MCP server.
3
- *
4
- * Creates a user-scoped client with JWT — RLS enforced automatically.
5
- * Never uses service role key (all queries respect user permissions).
6
- */
7
- import { createClient } from "@supabase/supabase-js";
8
- import { getValidToken } from "../auth/refresh.js";
9
- import { ATOMS_SUPABASE_URL, ATOMS_SUPABASE_ANON_KEY } from "../config.js";
10
- let cachedClient = null;
11
- let cachedUserId = null;
12
- let cachedEmail = null;
13
- let tokenExpiresAt = 0;
14
- /**
15
- * Get or create an authenticated Supabase client.
16
- * Auto-refreshes JWT when expired. All queries run with user's RLS permissions.
17
- */
18
- export async function getClient() {
19
- // If token is within 60s of expiry, force refresh
20
- if (cachedClient && tokenExpiresAt > Date.now() + 60_000) {
21
- return cachedClient;
22
- }
23
- // Token expired or no client — get a fresh token
24
- if (cachedClient) {
25
- process.stderr.write("[atoms-mcp] Token expiring, refreshing client...\n");
26
- }
27
- const { access_token, user_id, email } = await getValidToken();
28
- // Decode expiry from JWT
29
- const parts = access_token.split(".");
30
- if (parts.length === 3) {
31
- try {
32
- const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString());
33
- tokenExpiresAt = (payload.exp ?? 0) * 1000; // convert to ms
34
- }
35
- catch {
36
- // Fallback: assume 1hr from now
37
- tokenExpiresAt = Date.now() + 3600_000;
38
- }
39
- }
40
- // Use global Authorization header — works with all Supabase key formats
41
- const client = createClient(ATOMS_SUPABASE_URL, ATOMS_SUPABASE_ANON_KEY, {
42
- global: {
43
- headers: { Authorization: `Bearer ${access_token}` },
44
- },
45
- });
46
- cachedClient = client;
47
- cachedUserId = user_id;
48
- cachedEmail = email;
49
- return cachedClient;
50
- }
51
- /**
52
- * Get the authenticated user's ID. Must call getClient() first.
53
- */
54
- export function getUserId() {
55
- if (!cachedUserId) {
56
- throw new Error("Not authenticated. Call getClient() first.");
57
- }
58
- return cachedUserId;
59
- }
60
- /**
61
- * Get the authenticated user's email.
62
- */
63
- export function getUserEmail() {
64
- return cachedEmail ?? "";
65
- }
66
- /**
67
- * Reset the cached client (for token refresh).
68
- */
69
- export function resetClient() {
70
- cachedClient = null;
71
- cachedUserId = null;
72
- cachedEmail = null;
73
- tokenExpiresAt = 0;
74
- }
75
- /**
76
- * Check if the user has write access to a project.
77
- * Returns the user's org role, or throws a descriptive error.
78
- */
79
- export async function requireWriteAccess(client, projectId) {
80
- const userId = getUserId();
81
- // Get the project's org
82
- const { data: project, error: projErr } = await client
83
- .from("projects")
84
- .select("org_id")
85
- .eq("id", projectId)
86
- .maybeSingle();
87
- if (projErr || !project) {
88
- throw new Error(`Project '${projectId}' not found`);
89
- }
90
- // Get user's role in that org
91
- const { data: membership, error: memErr } = await client
92
- .from("org_members")
93
- .select("role")
94
- .eq("user_id", userId)
95
- .eq("org_id", project.org_id)
96
- .maybeSingle();
97
- if (memErr || !membership) {
98
- // Check if user is a platform admin (can access all orgs)
99
- const { data: adminCheck } = await client.rpc("is_platform_admin");
100
- if (adminCheck === true) {
101
- return "admin";
102
- }
103
- throw new Error("Not a member of this project's organization");
104
- }
105
- if (membership.role === "viewer") {
106
- throw new Error("VIEWER_ROLE");
107
- }
108
- return membership.role;
109
- }
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * Graph traversal helpers for traceability links.
4
- * Used by get-coverage and export-mermaid tools.
5
- *
6
- * TODO: Implement graph traversal in Phase 2.
7
- */
package/dist/db/graph.js DELETED
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * Graph traversal helpers for traceability links.
4
- * Used by get-coverage and export-mermaid tools.
5
- *
6
- * TODO: Implement graph traversal in Phase 2.
7
- */
@@ -1,76 +0,0 @@
1
- /**
2
- * Shared Supabase query helpers for MCP tools.
3
- *
4
- * All queries use the user's JWT via RLS — no service role key.
5
- * Query patterns adapted from supabase/functions/.../db.ts.
6
- */
7
- import type { SupabaseClient } from "@supabase/supabase-js";
8
- import type { ItemRow } from "../types/work-item.js";
9
- /**
10
- * List projects the authenticated user has access to (via org membership).
11
- */
12
- export declare function listProjects(client: SupabaseClient): Promise<Array<{
13
- id: string;
14
- name: string;
15
- description: string | null;
16
- org_id: string;
17
- org_name: string;
18
- created_at: string;
19
- }>>;
20
- /**
21
- * List items in a project with optional filters.
22
- * Returns rows ordered by created_at, respects soft deletes.
23
- */
24
- export declare function listItems(client: SupabaseClient, projectId: string, opts: {
25
- type?: string;
26
- domain?: string;
27
- level?: string;
28
- limit: number;
29
- offset: number;
30
- }): Promise<{
31
- items: ItemRow[];
32
- totalCount: number;
33
- }>;
34
- /**
35
- * Get a single item by ID (excludes soft-deleted).
36
- */
37
- export declare function getItemById(client: SupabaseClient, projectId: string, itemId: string): Promise<ItemRow | null>;
38
- /**
39
- * Full-text search across items in a project.
40
- * Uses ilike on title, body, and summary for broad matching.
41
- */
42
- export declare function searchItems(client: SupabaseClient, projectId: string, query: string, opts: {
43
- type?: string;
44
- limit: number;
45
- }): Promise<{
46
- items: ItemRow[];
47
- totalCount: number;
48
- }>;
49
- /**
50
- * Get all relationships for an item (both directions).
51
- */
52
- export declare function getItemRelationships(client: SupabaseClient, projectId: string, itemId: string): Promise<Array<{
53
- from_id: string;
54
- to_id: string;
55
- type: string;
56
- }>>;
57
- /**
58
- * Get test coverage report: requirements with/without linked test cases.
59
- */
60
- export declare function getCoverageReport(client: SupabaseClient, projectId: string, opts: {
61
- domain?: string;
62
- level?: string;
63
- }): Promise<{
64
- covered: ItemRow[];
65
- uncovered: ItemRow[];
66
- total: number;
67
- }>;
68
- /**
69
- * Get change history for an item, newest first.
70
- */
71
- export declare function getChangeHistory(client: SupabaseClient, projectId: string, itemId: string, limit: number): Promise<Array<Record<string, unknown>>>;
72
- /**
73
- * Generate the next item ID for a given type in a project.
74
- * Format: PREFIX-NNNNN (e.g., REQ-00058)
75
- */
76
- export declare function generateItemId(client: SupabaseClient, projectId: string, type: string): Promise<string>;
@@ -1,209 +0,0 @@
1
- /**
2
- * Shared Supabase query helpers for MCP tools.
3
- *
4
- * All queries use the user's JWT via RLS — no service role key.
5
- * Query patterns adapted from supabase/functions/.../db.ts.
6
- */
7
- // ---------------------------------------------------------------------------
8
- // Project queries
9
- // ---------------------------------------------------------------------------
10
- /**
11
- * List projects the authenticated user has access to (via org membership).
12
- */
13
- export async function listProjects(client) {
14
- const { data, error } = await client
15
- .from("projects")
16
- .select("id, name, description, org_id, created_at, organizations(name)")
17
- .is("deleted_at", null)
18
- .order("created_at", { ascending: false });
19
- if (error)
20
- throw new Error(error.message);
21
- return (data ?? []).map((p) => ({
22
- id: p.id,
23
- name: p.name,
24
- description: p.description,
25
- org_id: p.org_id,
26
- org_name: p.organizations?.name ?? "Unknown",
27
- created_at: p.created_at,
28
- }));
29
- }
30
- // ---------------------------------------------------------------------------
31
- // Item queries
32
- // ---------------------------------------------------------------------------
33
- /**
34
- * List items in a project with optional filters.
35
- * Returns rows ordered by created_at, respects soft deletes.
36
- */
37
- export async function listItems(client, projectId, opts) {
38
- let query = client
39
- .from("items")
40
- .select("*", { count: "exact" })
41
- .eq("project_id", projectId)
42
- .is("deleted_at", null)
43
- .order("created_at", { ascending: true })
44
- .range(opts.offset, opts.offset + opts.limit - 1);
45
- if (opts.type) {
46
- query = query.eq("type", opts.type);
47
- }
48
- const { data, error, count } = await query;
49
- if (error)
50
- throw new Error(error.message);
51
- // Apply domain/level filters in JS (JSONB nested field filtering)
52
- let filtered = (data ?? []);
53
- if (opts.domain) {
54
- filtered = filtered.filter((item) => {
55
- const domains = item.data?.tags?.domains ?? [];
56
- return domains.includes(opts.domain);
57
- });
58
- }
59
- if (opts.level) {
60
- filtered = filtered.filter((item) => {
61
- return item.data?.tags?.level === opts.level;
62
- });
63
- }
64
- return {
65
- items: filtered,
66
- totalCount: opts.domain || opts.level ? filtered.length : (count ?? 0),
67
- };
68
- }
69
- /**
70
- * Get a single item by ID (excludes soft-deleted).
71
- */
72
- export async function getItemById(client, projectId, itemId) {
73
- const { data, error } = await client
74
- .from("items")
75
- .select("*")
76
- .eq("id", itemId)
77
- .eq("project_id", projectId)
78
- .is("deleted_at", null)
79
- .maybeSingle();
80
- if (error)
81
- throw new Error(error.message);
82
- return data;
83
- }
84
- /**
85
- * Full-text search across items in a project.
86
- * Uses ilike on title, body, and summary for broad matching.
87
- */
88
- export async function searchItems(client, projectId, query, opts) {
89
- const searchTerm = query.trim();
90
- if (!searchTerm) {
91
- return { items: [], totalCount: 0 };
92
- }
93
- // Use ilike for broad matching across title and JSONB fields
94
- let queryBuilder = client
95
- .from("items")
96
- .select("*", { count: "exact" })
97
- .eq("project_id", projectId)
98
- .is("deleted_at", null)
99
- .or(`title.ilike.%${searchTerm}%,` +
100
- `data->>body.ilike.%${searchTerm}%,` +
101
- `data->>summary.ilike.%${searchTerm}%`)
102
- .limit(opts.limit);
103
- if (opts.type) {
104
- queryBuilder = queryBuilder.eq("type", opts.type);
105
- }
106
- const { data, error, count } = await queryBuilder;
107
- if (error)
108
- throw new Error(error.message);
109
- return { items: (data ?? []), totalCount: count ?? 0 };
110
- }
111
- // ---------------------------------------------------------------------------
112
- // Relationship queries
113
- // ---------------------------------------------------------------------------
114
- /**
115
- * Get all relationships for an item (both directions).
116
- */
117
- export async function getItemRelationships(client, projectId, itemId) {
118
- const { data, error } = await client
119
- .from("item_relationships")
120
- .select("from_id, to_id, type")
121
- .eq("project_id", projectId)
122
- .or(`from_id.eq.${itemId},to_id.eq.${itemId}`);
123
- if (error)
124
- throw new Error(error.message);
125
- return data ?? [];
126
- }
127
- // ---------------------------------------------------------------------------
128
- // Coverage queries
129
- // ---------------------------------------------------------------------------
130
- /**
131
- * Get test coverage report: requirements with/without linked test cases.
132
- */
133
- export async function getCoverageReport(client, projectId, opts) {
134
- // Get all requirements in the project
135
- const { data: requirements, error: reqErr } = await client
136
- .from("items")
137
- .select("*")
138
- .eq("project_id", projectId)
139
- .eq("type", "requirement")
140
- .is("deleted_at", null);
141
- if (reqErr)
142
- throw new Error(reqErr.message);
143
- const allReqs = (requirements ?? []);
144
- // Filter by domain/level on the JSONB data
145
- const filteredReqs = allReqs.filter((req) => {
146
- if (opts.domain) {
147
- const domains = req.data?.tags?.domains ?? [];
148
- if (!domains.includes(opts.domain))
149
- return false;
150
- }
151
- if (opts.level) {
152
- if (req.data?.tags?.level !== opts.level)
153
- return false;
154
- }
155
- return true;
156
- });
157
- // Get all verified_by relationships for this project
158
- const { data: verifiedRels, error: relErr } = await client
159
- .from("item_relationships")
160
- .select("from_id")
161
- .eq("project_id", projectId)
162
- .eq("type", "verified_by");
163
- if (relErr)
164
- throw new Error(relErr.message);
165
- const coveredIds = new Set((verifiedRels ?? []).map((r) => r.from_id));
166
- const covered = filteredReqs.filter((r) => coveredIds.has(r.id));
167
- const uncovered = filteredReqs.filter((r) => !coveredIds.has(r.id));
168
- return { covered, uncovered, total: filteredReqs.length };
169
- }
170
- // ---------------------------------------------------------------------------
171
- // History queries
172
- // ---------------------------------------------------------------------------
173
- /**
174
- * Get change history for an item, newest first.
175
- */
176
- export async function getChangeHistory(client, projectId, itemId, limit) {
177
- const { data, error } = await client
178
- .from("change_history")
179
- .select("*")
180
- .eq("item_id", itemId)
181
- .eq("project_id", projectId)
182
- .order("changed_at", { ascending: false })
183
- .limit(limit);
184
- if (error)
185
- throw new Error(error.message);
186
- return data ?? [];
187
- }
188
- // ---------------------------------------------------------------------------
189
- // Item mutations (for write tools — Phase 3)
190
- // ---------------------------------------------------------------------------
191
- /**
192
- * Generate the next item ID for a given type in a project.
193
- * Format: PREFIX-NNNNN (e.g., REQ-00058)
194
- */
195
- export async function generateItemId(client, projectId, type) {
196
- const prefix = type === "requirement" ? "REQ"
197
- : type === "test-case" ? "TC"
198
- : type === "note" ? "NOTE"
199
- : "TABLE";
200
- // Use Postgres SECURITY DEFINER function that sees ALL items (including soft-deleted)
201
- // to avoid ID collisions. Only returns the next ID string, never exposes item data.
202
- const { data, error } = await client.rpc("next_item_id", {
203
- p_project_id: projectId,
204
- p_prefix: prefix,
205
- });
206
- if (error)
207
- throw new Error(`ID generation failed: ${error.message}`);
208
- return data;
209
- }
package/dist/index.d.ts DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ATOMS MCP Server — CLI Entry Point
4
- *
5
- * Commands:
6
- * npx @atoms-tech/atoms-mcp → Start MCP server (stdio transport)
7
- * npx @atoms-tech/atoms-mcp login → Authenticate with ATOMS.tech
8
- * npx @atoms-tech/atoms-mcp whoami → Show current user
9
- * npx @atoms-tech/atoms-mcp --version → Show version
10
- */
11
- export {};
@@ -1,25 +0,0 @@
1
- /**
2
- * Fire-and-forget MCP audit logging.
3
- *
4
- * Every tool call is logged to mcp_audit_log for enterprise compliance.
5
- * Logging NEVER blocks or delays the tool response.
6
- */
7
- import type { SupabaseClient } from "@supabase/supabase-js";
8
- export interface AuditEntry {
9
- tool_name: string;
10
- params: Record<string, unknown>;
11
- status: "success" | "error";
12
- duration_ms: number;
13
- error_msg?: string;
14
- project_id?: string;
15
- session_id?: string;
16
- client_name?: string;
17
- }
18
- /**
19
- * Log a tool call to mcp_audit_log. Fire-and-forget — errors are swallowed.
20
- */
21
- export declare function logAudit(client: SupabaseClient, userId: string, entry: AuditEntry): void;
22
- /**
23
- * Timer utility for measuring tool duration.
24
- */
25
- export declare function startTimer(): () => number;
@@ -1,43 +0,0 @@
1
- /**
2
- * Fire-and-forget MCP audit logging.
3
- *
4
- * Every tool call is logged to mcp_audit_log for enterprise compliance.
5
- * Logging NEVER blocks or delays the tool response.
6
- */
7
- /**
8
- * Log a tool call to mcp_audit_log. Fire-and-forget — errors are swallowed.
9
- */
10
- export function logAudit(client, userId, entry) {
11
- // Sanitize params — strip any secrets
12
- const sanitizedParams = { ...entry.params };
13
- delete sanitizedParams.access_token;
14
- delete sanitizedParams.refresh_token;
15
- delete sanitizedParams.password;
16
- // Fire and forget — don't await
17
- client
18
- .from("mcp_audit_log")
19
- .insert({
20
- user_id: userId,
21
- tool_name: entry.tool_name,
22
- params: sanitizedParams,
23
- status: entry.status,
24
- duration_ms: entry.duration_ms,
25
- error_msg: entry.error_msg ?? null,
26
- project_id: entry.project_id ?? null,
27
- session_id: entry.session_id ?? null,
28
- client_name: entry.client_name ?? null,
29
- })
30
- .then(({ error }) => {
31
- if (error) {
32
- // Log to stderr, never throw
33
- process.stderr.write(`[audit] Failed to log: ${error.message}\n`);
34
- }
35
- });
36
- }
37
- /**
38
- * Timer utility for measuring tool duration.
39
- */
40
- export function startTimer() {
41
- const start = performance.now();
42
- return () => Math.round(performance.now() - start);
43
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * In-memory per-user rate limiting for MCP tool calls.
3
- *
4
- * Default: 60 requests/minute. Configurable via ATOMS_RATE_LIMIT_RPM env var.
5
- * Uses sliding window algorithm.
6
- */
7
- /**
8
- * Check if a user has exceeded their rate limit.
9
- * Returns { allowed: true } or { allowed: false, retryAfterSeconds }.
10
- */
11
- export declare function checkRateLimit(userId: string): {
12
- allowed: true;
13
- } | {
14
- allowed: false;
15
- retryAfterSeconds: number;
16
- };
17
- /**
18
- * Clear rate limit state. Useful for testing.
19
- */
20
- export declare function clearRateLimits(): void;
@@ -1,42 +0,0 @@
1
- /**
2
- * In-memory per-user rate limiting for MCP tool calls.
3
- *
4
- * Default: 60 requests/minute. Configurable via ATOMS_RATE_LIMIT_RPM env var.
5
- * Uses sliding window algorithm.
6
- */
7
- const DEFAULT_RPM = 60;
8
- const WINDOW_MS = 60_000; // 1 minute
9
- const windows = new Map();
10
- /**
11
- * Check if a user has exceeded their rate limit.
12
- * Returns { allowed: true } or { allowed: false, retryAfterSeconds }.
13
- */
14
- export function checkRateLimit(userId) {
15
- const rpm = parseInt(process.env.ATOMS_RATE_LIMIT_RPM ?? "", 10) || DEFAULT_RPM;
16
- const now = Date.now();
17
- const cutoff = now - WINDOW_MS;
18
- let window = windows.get(userId);
19
- if (!window) {
20
- window = { timestamps: [] };
21
- windows.set(userId, window);
22
- }
23
- // Prune old timestamps
24
- window.timestamps = window.timestamps.filter((t) => t > cutoff);
25
- if (window.timestamps.length >= rpm) {
26
- // Calculate when the oldest request in the window expires
27
- const oldestInWindow = window.timestamps[0];
28
- const retryAfterMs = oldestInWindow + WINDOW_MS - now;
29
- return {
30
- allowed: false,
31
- retryAfterSeconds: Math.ceil(retryAfterMs / 1000),
32
- };
33
- }
34
- window.timestamps.push(now);
35
- return { allowed: true };
36
- }
37
- /**
38
- * Clear rate limit state. Useful for testing.
39
- */
40
- export function clearRateLimits() {
41
- windows.clear();
42
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Input validation for MCP tool parameters.
3
- *
4
- * All LLM-generated inputs are untrusted. Validate everything.
5
- * Pattern ported from Polarion MCP middleware/input_validator.py.
6
- */
7
- export declare function validateProjectId(id: string): string;
8
- export declare function validateItemId(id: string): string;
9
- export declare function validateQuery(query: string): string;
10
- export declare function validateLimit(limit: number, max: number): number;
11
- export declare function validateOffset(offset: number): number;
12
- export declare function validateTitle(title: string): string;
13
- export declare function validateItemType(type: string): string;
14
- export declare function validateRelationshipType(type: string): string;
15
- export declare function sanitizeText(text: string): string;
16
- /**
17
- * Custom validation error with a clean message for LLM consumption.
18
- */
19
- export declare class ValidationError extends Error {
20
- constructor(message: string);
21
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Input validation for MCP tool parameters.
3
- *
4
- * All LLM-generated inputs are untrusted. Validate everything.
5
- * Pattern ported from Polarion MCP middleware/input_validator.py.
6
- */
7
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
- const ITEM_ID_REGEX = /^[A-Z]+-\d{5}$/;
9
- const SAFE_TEXT_REGEX = /^[^<>&;'"\\]+$/;
10
- export function validateProjectId(id) {
11
- if (!id || typeof id !== "string") {
12
- throw new ValidationError("project_id is required");
13
- }
14
- if (!UUID_REGEX.test(id)) {
15
- throw new ValidationError("project_id must be a valid UUID");
16
- }
17
- return id;
18
- }
19
- export function validateItemId(id) {
20
- if (!id || typeof id !== "string") {
21
- throw new ValidationError("item_id is required");
22
- }
23
- if (id.length > 20) {
24
- throw new ValidationError("item_id exceeds maximum length");
25
- }
26
- if (!ITEM_ID_REGEX.test(id)) {
27
- throw new ValidationError("item_id must match format: PREFIX-NNNNN (e.g., REQ-00001, TC-00050)");
28
- }
29
- return id;
30
- }
31
- export function validateQuery(query) {
32
- if (!query || typeof query !== "string") {
33
- throw new ValidationError("query is required");
34
- }
35
- if (query.length > 1000) {
36
- throw new ValidationError("query exceeds maximum length of 1000 characters");
37
- }
38
- return query.trim();
39
- }
40
- export function validateLimit(limit, max) {
41
- if (typeof limit !== "number" || !Number.isInteger(limit)) {
42
- throw new ValidationError("limit must be an integer");
43
- }
44
- return Math.max(1, Math.min(limit, max));
45
- }
46
- export function validateOffset(offset) {
47
- if (typeof offset !== "number" || !Number.isInteger(offset)) {
48
- throw new ValidationError("offset must be an integer");
49
- }
50
- return Math.max(0, offset);
51
- }
52
- export function validateTitle(title) {
53
- if (!title || typeof title !== "string") {
54
- throw new ValidationError("title is required");
55
- }
56
- if (title.length > 500) {
57
- throw new ValidationError("title exceeds maximum length of 500 characters");
58
- }
59
- return title.trim();
60
- }
61
- export function validateItemType(type) {
62
- const validTypes = ["requirement", "test-case", "note", "table"];
63
- if (!validTypes.includes(type)) {
64
- throw new ValidationError(`type must be one of: ${validTypes.join(", ")}. Got: '${type}'`);
65
- }
66
- return type;
67
- }
68
- export function validateRelationshipType(type) {
69
- const validTypes = ["parent", "child", "related", "verifies", "verified_by"];
70
- if (!validTypes.includes(type)) {
71
- throw new ValidationError(`relationship type must be one of: ${validTypes.join(", ")}. Got: '${type}'`);
72
- }
73
- return type;
74
- }
75
- export function sanitizeText(text) {
76
- // Strip potential XSS/injection characters from free-text fields
77
- return text
78
- .replace(/[<>&]/g, "")
79
- .replace(/\\/g, "")
80
- .trim();
81
- }
82
- /**
83
- * Custom validation error with a clean message for LLM consumption.
84
- */
85
- export class ValidationError extends Error {
86
- constructor(message) {
87
- super(message);
88
- this.name = "ValidationError";
89
- }
90
- }
package/dist/server.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * ATOMS MCP Server — Tool registration and initialization.
3
- *
4
- * Registers 15 tools: 10 via server.registerTool(), 5 as MCP Apps via registerAtomsApp().
5
- * MCP App tools (mermaid, summary, trace) render interactive UIs in capable hosts
6
- * while falling back to text for non-UI clients.
7
- *
8
- * Naming: atoms_{action}_{resource} (snake_case with service prefix)
9
- * Server name: atoms-mcp-server (follows {service}-mcp-server convention)
10
- */
11
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
- export declare const server: McpServer;
13
- /** Expose session ID for change_history writes */
14
- export declare function getMcpSessionId(): string;