@grafema/mcp 0.2.0-beta → 0.2.5-beta

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/src/handlers.ts CHANGED
@@ -2,11 +2,14 @@
2
2
  * MCP Tool Handlers
3
3
  */
4
4
 
5
- import { join } from 'path';
6
5
  import { ensureAnalyzed } from './analysis.js';
7
6
  import { getProjectPath, getAnalysisStatus, getOrCreateBackend, getGuaranteeManager, getGuaranteeAPI, isAnalysisRunning } from './state.js';
8
- import { CoverageAnalyzer, findCallsInFunction, findContainingFunction } from '@grafema/core';
7
+ import { CoverageAnalyzer, findCallsInFunction, findContainingFunction, validateServices, validatePatterns, validateWorkspace, getOnboardingInstruction } from '@grafema/core';
9
8
  import type { CallInfo, CallerInfo } from '@grafema/core';
9
+ import { existsSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'fs';
10
+ import type { Dirent } from 'fs';
11
+ import { join, basename } from 'path';
12
+ import { stringify as stringifyYAML } from 'yaml';
10
13
  import {
11
14
  normalizeLimit,
12
15
  formatPaginationInfo,
@@ -34,8 +37,12 @@ import type {
34
37
  FindGuardsArgs,
35
38
  GuardInfo,
36
39
  GetFunctionDetailsArgs,
37
- GraphBackend,
38
40
  GraphNode,
41
+ DatalogBinding,
42
+ CallResult,
43
+ ReportIssueArgs,
44
+ ReadProjectStructureArgs,
45
+ WriteConfigArgs,
39
46
  } from './types.js';
40
47
  import { isGuaranteeType } from '@grafema/core';
41
48
 
@@ -43,8 +50,7 @@ import { isGuaranteeType } from '@grafema/core';
43
50
 
44
51
  export async function handleQueryGraph(args: QueryGraphArgs): Promise<ToolResult> {
45
52
  const db = await ensureAnalyzed();
46
- const { query, limit: requestedLimit, offset: requestedOffset, format } = args;
47
- const explain = (args as any).explain;
53
+ const { query, limit: requestedLimit, offset: requestedOffset, format: _format, explain: _explain } = args;
48
54
 
49
55
  const limit = normalizeLimit(requestedLimit);
50
56
  const offset = Math.max(0, requestedOffset || 0);
@@ -87,7 +93,7 @@ export async function handleQueryGraph(args: QueryGraphArgs): Promise<ToolResult
87
93
 
88
94
  const enrichedResults: unknown[] = [];
89
95
  for (const result of paginatedResults) {
90
- const nodeId = result.bindings?.find((b: any) => b.name === 'X')?.value;
96
+ const nodeId = result.bindings?.find((b: DatalogBinding) => b.name === 'X')?.value;
91
97
  if (nodeId) {
92
98
  const node = await db.getNode(nodeId);
93
99
  if (node) {
@@ -117,25 +123,25 @@ export async function handleQueryGraph(args: QueryGraphArgs): Promise<ToolResult
117
123
 
118
124
  return textResult(guardResponseSize(responseText));
119
125
  } catch (error) {
120
- return errorResult((error as Error).message);
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ return errorResult(message);
121
128
  }
122
129
  }
123
130
 
124
131
  export async function handleFindCalls(args: FindCallsArgs): Promise<ToolResult> {
125
132
  const db = await ensureAnalyzed();
126
- const { target: name, limit: requestedLimit, offset: requestedOffset } = args;
127
- const className = (args as any).className;
133
+ const { target: name, limit: requestedLimit, offset: requestedOffset, className } = args;
128
134
 
129
135
  const limit = normalizeLimit(requestedLimit);
130
136
  const offset = Math.max(0, requestedOffset || 0);
131
137
 
132
- const calls: unknown[] = [];
138
+ const calls: CallResult[] = [];
133
139
  let skipped = 0;
134
140
  let totalMatched = 0;
135
141
 
136
142
  for await (const node of db.queryNodes({ type: 'CALL' })) {
137
- if ((node as any).name !== name && (node as any).method !== name) continue;
138
- if (className && (node as any).object !== className) continue;
143
+ if (node.name !== name && node['method'] !== name) continue;
144
+ if (className && node['object'] !== className) continue;
139
145
 
140
146
  totalMatched++;
141
147
 
@@ -155,7 +161,7 @@ export async function handleFindCalls(args: FindCallsArgs): Promise<ToolResult>
155
161
  target = targetNode
156
162
  ? {
157
163
  type: targetNode.type,
158
- name: targetNode.name,
164
+ name: targetNode.name ?? '',
159
165
  file: targetNode.file,
160
166
  line: targetNode.line,
161
167
  }
@@ -164,8 +170,8 @@ export async function handleFindCalls(args: FindCallsArgs): Promise<ToolResult>
164
170
 
165
171
  calls.push({
166
172
  id: node.id,
167
- name: (node as any).name,
168
- object: (node as any).object,
173
+ name: node.name,
174
+ object: node['object'] as string | undefined,
169
175
  file: node.file,
170
176
  line: node.line,
171
177
  resolved: isResolved,
@@ -177,7 +183,7 @@ export async function handleFindCalls(args: FindCallsArgs): Promise<ToolResult>
177
183
  return textResult(`No calls found for "${className ? className + '.' : ''}${name}"`);
178
184
  }
179
185
 
180
- const resolved = calls.filter((c: any) => c.resolved).length;
186
+ const resolved = calls.filter(c => c.resolved).length;
181
187
  const unresolved = calls.length - resolved;
182
188
  const hasMore = offset + calls.length < totalMatched;
183
189
 
@@ -254,7 +260,7 @@ export async function handleFindNodes(args: FindNodesArgs): Promise<ToolResult>
254
260
  export async function handleTraceAlias(args: TraceAliasArgs): Promise<ToolResult> {
255
261
  const db = await ensureAnalyzed();
256
262
  const { variableName, file } = args;
257
- const projectPath = getProjectPath();
263
+ const _projectPath = getProjectPath();
258
264
 
259
265
  let varNode: GraphNode | null = null;
260
266
 
@@ -419,7 +425,8 @@ export async function handleCheckInvariant(args: CheckInvariantArgs): Promise<To
419
425
  )}${total > 20 ? `\n\n... and ${total - 20} more` : ''}`
420
426
  );
421
427
  } catch (error) {
422
- return errorResult((error as Error).message);
428
+ const message = error instanceof Error ? error.message : String(error);
429
+ return errorResult(message);
423
430
  }
424
431
  }
425
432
 
@@ -451,7 +458,8 @@ export async function handleAnalyzeProject(args: AnalyzeProjectArgs): Promise<To
451
458
  `- Total time: ${status.timings.total || 'N/A'}s`
452
459
  );
453
460
  } catch (error) {
454
- return errorResult((error as Error).message);
461
+ const message = error instanceof Error ? error.message : String(error);
462
+ return errorResult(message);
455
463
  }
456
464
  }
457
465
 
@@ -576,7 +584,8 @@ export async function handleCreateGuarantee(args: CreateGuaranteeArgs): Promise<
576
584
  );
577
585
  }
578
586
  } catch (error) {
579
- return errorResult(`Failed to create guarantee: ${(error as Error).message}`);
587
+ const message = error instanceof Error ? error.message : String(error);
588
+ return errorResult(`Failed to create guarantee: ${message}`);
580
589
  }
581
590
  }
582
591
 
@@ -622,7 +631,8 @@ export async function handleListGuarantees(): Promise<ToolResult> {
622
631
 
623
632
  return textResult(results.join('\n'));
624
633
  } catch (error) {
625
- return errorResult(`Failed to list guarantees: ${(error as Error).message}`);
634
+ const message = error instanceof Error ? error.message : String(error);
635
+ return errorResult(`Failed to list guarantees: ${message}`);
626
636
  }
627
637
  }
628
638
 
@@ -731,7 +741,8 @@ export async function handleCheckGuarantees(args: CheckGuaranteesArgs): Promise<
731
741
  const summary = `\n---\nTotal: ${totalPassed + totalFailed} | ✅ Passed: ${totalPassed} | ❌ Failed: ${totalFailed}`;
732
742
  return textResult(results.join('\n') + summary);
733
743
  } catch (error) {
734
- return errorResult(`Failed to check guarantees: ${(error as Error).message}`);
744
+ const message = error instanceof Error ? error.message : String(error);
745
+ return errorResult(`Failed to check guarantees: ${message}`);
735
746
  }
736
747
  }
737
748
 
@@ -766,7 +777,8 @@ export async function handleDeleteGuarantee(args: DeleteGuaranteeArgs): Promise<
766
777
 
767
778
  return errorResult(`Guarantee not found: ${name}`);
768
779
  } catch (error) {
769
- return errorResult(`Failed to delete guarantee: ${(error as Error).message}`);
780
+ const message = error instanceof Error ? error.message : String(error);
781
+ return errorResult(`Failed to delete guarantee: ${message}`);
770
782
  }
771
783
  }
772
784
 
@@ -807,7 +819,8 @@ export async function handleGetCoverage(args: GetCoverageArgs): Promise<ToolResu
807
819
 
808
820
  return textResult(output);
809
821
  } catch (error) {
810
- return errorResult(`Failed to calculate coverage: ${(error as Error).message}`);
822
+ const message = error instanceof Error ? error.message : String(error);
823
+ return errorResult(`Failed to calculate coverage: ${message}`);
811
824
  }
812
825
  }
813
826
 
@@ -815,6 +828,7 @@ export async function handleGetDocumentation(args: GetDocumentationArgs): Promis
815
828
  const { topic = 'overview' } = args;
816
829
 
817
830
  const docs: Record<string, string> = {
831
+ onboarding: getOnboardingInstruction(),
818
832
  overview: `
819
833
  # Grafema Code Analysis
820
834
 
@@ -855,7 +869,7 @@ Find unresolved calls:
855
869
 
856
870
  ## Core Node Types
857
871
  - MODULE, FUNCTION, CLASS, METHOD, VARIABLE
858
- - CALL, IMPORT, EXPORT, PARAMETER
872
+ - CALL, PROPERTY_ACCESS, IMPORT, EXPORT, PARAMETER
859
873
 
860
874
  ## HTTP/Network
861
875
  - http:route, http:request, db:query
@@ -1111,7 +1125,7 @@ function formatCallsForDisplay(calls: CallInfo[]): string[] {
1111
1125
 
1112
1126
  // === BUG REPORTING ===
1113
1127
 
1114
- export async function handleReportIssue(args: import('./types.js').ReportIssueArgs): Promise<ToolResult> {
1128
+ export async function handleReportIssue(args: ReportIssueArgs): Promise<ToolResult> {
1115
1129
  const { title, description, context, labels = ['bug'] } = args;
1116
1130
  // Use user's token if provided, otherwise fall back to project's issue-only token
1117
1131
  const GRAFEMA_ISSUE_TOKEN = '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';
@@ -1178,3 +1192,182 @@ ${context ? `## Context\n\`\`\`\n${context}\n\`\`\`\n` : ''}
1178
1192
  `---\n**Title:** ${title}\n\n${body}\n---`
1179
1193
  );
1180
1194
  }
1195
+
1196
+ // === PROJECT STRUCTURE (REG-173) ===
1197
+
1198
+ export async function handleReadProjectStructure(
1199
+ args: ReadProjectStructureArgs
1200
+ ): Promise<ToolResult> {
1201
+ const projectPath = getProjectPath();
1202
+ const subPath = args.path || '.';
1203
+ const maxDepth = Math.min(Math.max(1, args.depth || 3), 5);
1204
+ const includeFiles = args.include_files !== false;
1205
+
1206
+ const targetPath = join(projectPath, subPath);
1207
+
1208
+ if (!existsSync(targetPath)) {
1209
+ return errorResult(`Path does not exist: ${subPath}`);
1210
+ }
1211
+
1212
+ if (!statSync(targetPath).isDirectory()) {
1213
+ return errorResult(`Path is not a directory: ${subPath}`);
1214
+ }
1215
+
1216
+ const EXCLUDED = new Set([
1217
+ 'node_modules', '.git', 'dist', 'build', '.grafema',
1218
+ 'coverage', '.next', '.nuxt', '.cache', '.output',
1219
+ '__pycache__', '.tox', 'target',
1220
+ ]);
1221
+
1222
+ const lines: string[] = [];
1223
+
1224
+ function walk(dir: string, prefix: string, depth: number): void {
1225
+ if (depth > maxDepth) return;
1226
+
1227
+ let entries: Dirent[];
1228
+ try {
1229
+ entries = readdirSync(dir, { withFileTypes: true });
1230
+ } catch {
1231
+ return;
1232
+ }
1233
+
1234
+ const dirs: string[] = [];
1235
+ const files: string[] = [];
1236
+
1237
+ for (const entry of entries) {
1238
+ if (EXCLUDED.has(entry.name)) continue;
1239
+
1240
+ if (entry.isDirectory()) {
1241
+ dirs.push(entry.name);
1242
+ } else if (includeFiles) {
1243
+ files.push(entry.name);
1244
+ }
1245
+ }
1246
+
1247
+ dirs.sort();
1248
+ files.sort();
1249
+
1250
+ const allEntries = [
1251
+ ...dirs.map(d => ({ name: d, isDir: true })),
1252
+ ...files.map(f => ({ name: f, isDir: false })),
1253
+ ];
1254
+
1255
+ for (let i = 0; i < allEntries.length; i++) {
1256
+ const entry = allEntries[i];
1257
+ const isLast = i === allEntries.length - 1;
1258
+ const connector = isLast ? '└── ' : '├── ';
1259
+ const childPrefix = isLast ? ' ' : '│ ';
1260
+
1261
+ if (entry.isDir) {
1262
+ lines.push(`${prefix}${connector}${entry.name}/`);
1263
+ walk(join(dir, entry.name), prefix + childPrefix, depth + 1);
1264
+ } else {
1265
+ lines.push(`${prefix}${connector}${entry.name}`);
1266
+ }
1267
+ }
1268
+ }
1269
+
1270
+ lines.push(subPath === '.' ? basename(projectPath) + '/' : subPath + '/');
1271
+ walk(targetPath, '', 1);
1272
+
1273
+ if (lines.length === 1) {
1274
+ return textResult(`Directory is empty or contains only excluded entries: ${subPath}`);
1275
+ }
1276
+
1277
+ return textResult(lines.join('\n'));
1278
+ }
1279
+
1280
+ // === WRITE CONFIG (REG-173) ===
1281
+
1282
+ export async function handleWriteConfig(
1283
+ args: WriteConfigArgs
1284
+ ): Promise<ToolResult> {
1285
+ const projectPath = getProjectPath();
1286
+ const grafemaDir = join(projectPath, '.grafema');
1287
+ const configPath = join(grafemaDir, 'config.yaml');
1288
+
1289
+ try {
1290
+ if (args.services) {
1291
+ validateServices(args.services, projectPath);
1292
+ }
1293
+
1294
+ if (args.include !== undefined || args.exclude !== undefined) {
1295
+ const warnings: string[] = [];
1296
+ validatePatterns(args.include, args.exclude, {
1297
+ warn: (msg: string) => warnings.push(msg),
1298
+ });
1299
+ }
1300
+
1301
+ if (args.workspace) {
1302
+ validateWorkspace(args.workspace, projectPath);
1303
+ }
1304
+
1305
+ const config: Record<string, unknown> = {};
1306
+
1307
+ if (args.services && args.services.length > 0) {
1308
+ config.services = args.services;
1309
+ }
1310
+
1311
+ if (args.plugins) {
1312
+ config.plugins = args.plugins;
1313
+ }
1314
+
1315
+ if (args.include) {
1316
+ config.include = args.include;
1317
+ }
1318
+
1319
+ if (args.exclude) {
1320
+ config.exclude = args.exclude;
1321
+ }
1322
+
1323
+ if (args.workspace) {
1324
+ config.workspace = args.workspace;
1325
+ }
1326
+
1327
+ const yaml = stringifyYAML(config, { lineWidth: 0 });
1328
+ const content =
1329
+ '# Grafema Configuration\n' +
1330
+ '# Generated by Grafema onboarding\n' +
1331
+ '# Documentation: https://github.com/grafema/grafema#configuration\n\n' +
1332
+ yaml;
1333
+
1334
+ if (!existsSync(grafemaDir)) {
1335
+ mkdirSync(grafemaDir, { recursive: true });
1336
+ }
1337
+
1338
+ writeFileSync(configPath, content);
1339
+
1340
+ const summary: string[] = ['Configuration written to .grafema/config.yaml'];
1341
+
1342
+ if (args.services && args.services.length > 0) {
1343
+ summary.push(`Services: ${args.services.map(s => s.name).join(', ')}`);
1344
+ } else {
1345
+ summary.push('Services: using auto-discovery (none explicitly configured)');
1346
+ }
1347
+
1348
+ if (args.plugins) {
1349
+ summary.push('Plugins: custom configuration');
1350
+ } else {
1351
+ summary.push('Plugins: using defaults');
1352
+ }
1353
+
1354
+ if (args.include) {
1355
+ summary.push(`Include patterns: ${args.include.join(', ')}`);
1356
+ }
1357
+
1358
+ if (args.exclude) {
1359
+ summary.push(`Exclude patterns: ${args.exclude.join(', ')}`);
1360
+ }
1361
+
1362
+ if (args.workspace?.roots) {
1363
+ summary.push(`Workspace roots: ${args.workspace.roots.join(', ')}`);
1364
+ }
1365
+
1366
+ summary.push('\nNext step: run analyze_project to build the graph.');
1367
+
1368
+ return textResult(summary.join('\n'));
1369
+ } catch (error) {
1370
+ const message = error instanceof Error ? error.message : String(error);
1371
+ return errorResult(`Failed to write config: ${message}`);
1372
+ }
1373
+ }
package/src/prompts.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * MCP Prompts handler logic.
3
+ * Extracted for testability — server.ts is a thin wrapper.
4
+ */
5
+ import { getOnboardingInstruction } from '@grafema/core';
6
+
7
+ export interface PromptDefinition {
8
+ name: string;
9
+ description: string;
10
+ arguments: Array<{ name: string; description: string; required?: boolean }>;
11
+ }
12
+
13
+ export interface PromptMessage {
14
+ role: 'user' | 'assistant';
15
+ content: { type: 'text'; text: string };
16
+ }
17
+
18
+ export interface PromptResult {
19
+ [x: string]: unknown;
20
+ description: string;
21
+ messages: PromptMessage[];
22
+ _meta?: Record<string, unknown>;
23
+ }
24
+
25
+ export const PROMPTS: PromptDefinition[] = [
26
+ {
27
+ name: 'onboard_project',
28
+ description:
29
+ 'Step-by-step instructions for studying a new project and ' +
30
+ 'configuring Grafema for analysis. Use this when setting up ' +
31
+ 'Grafema for the first time on a project.',
32
+ arguments: [],
33
+ },
34
+ ];
35
+
36
+ export function getPrompt(name: string): PromptResult {
37
+ if (name === 'onboard_project') {
38
+ const instruction = getOnboardingInstruction();
39
+ return {
40
+ description: PROMPTS[0].description,
41
+ messages: [
42
+ {
43
+ role: 'user',
44
+ content: {
45
+ type: 'text',
46
+ text: instruction,
47
+ },
48
+ },
49
+ ],
50
+ };
51
+ }
52
+
53
+ throw new Error(
54
+ `Unknown prompt: ${name}. Available prompts: ${PROMPTS.map(p => p.name).join(', ')}`
55
+ );
56
+ }
package/src/server.ts CHANGED
@@ -10,12 +10,15 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
10
10
  import {
11
11
  CallToolRequestSchema,
12
12
  ListToolsRequestSchema,
13
+ ListPromptsRequestSchema,
14
+ GetPromptRequestSchema,
13
15
  } from '@modelcontextprotocol/sdk/types.js';
16
+ import { PROMPTS, getPrompt } from './prompts.js';
14
17
 
15
18
  import { TOOLS } from './definitions.js';
16
19
  import { initializeFromArgs, setupLogging, getProjectPath } from './state.js';
17
20
  import { textResult, errorResult, log } from './utils.js';
18
- import { ensureAnalyzed, discoverServices } from './analysis.js';
21
+ import { discoverServices } from './analysis.js';
19
22
  import {
20
23
  handleQueryGraph,
21
24
  handleFindCalls,
@@ -36,8 +39,39 @@ import {
36
39
  handleFindGuards,
37
40
  handleReportIssue,
38
41
  handleGetFunctionDetails,
42
+ handleReadProjectStructure,
43
+ handleWriteConfig,
39
44
  } from './handlers.js';
40
- import type { ToolResult, ReportIssueArgs, GetDocumentationArgs, GetFunctionDetailsArgs } from './types.js';
45
+ import type {
46
+ ToolResult,
47
+ ReportIssueArgs,
48
+ GetDocumentationArgs,
49
+ GetFunctionDetailsArgs,
50
+ QueryGraphArgs,
51
+ FindCallsArgs,
52
+ FindNodesArgs,
53
+ TraceAliasArgs,
54
+ TraceDataFlowArgs,
55
+ CheckInvariantArgs,
56
+ AnalyzeProjectArgs,
57
+ GetSchemaArgs,
58
+ CreateGuaranteeArgs,
59
+ CheckGuaranteesArgs,
60
+ DeleteGuaranteeArgs,
61
+ GetCoverageArgs,
62
+ FindGuardsArgs,
63
+ ReadProjectStructureArgs,
64
+ WriteConfigArgs,
65
+ } from './types.js';
66
+
67
+ /**
68
+ * Type-safe argument casting helper.
69
+ * MCP SDK provides args as Record<string, unknown>, this helper
70
+ * casts them to the expected handler argument type.
71
+ */
72
+ function asArgs<T>(args: Record<string, unknown> | undefined): T {
73
+ return (args ?? {}) as T;
74
+ }
41
75
 
42
76
  // Initialize from command line args
43
77
  initializeFromArgs();
@@ -55,6 +89,7 @@ const server = new Server(
55
89
  {
56
90
  capabilities: {
57
91
  tools: {},
92
+ prompts: {},
58
93
  },
59
94
  }
60
95
  );
@@ -64,6 +99,16 @@ server.setRequestHandler(ListToolsRequestSchema, async (_request, _extra) => {
64
99
  return { tools: TOOLS };
65
100
  });
66
101
 
102
+ // List available prompts
103
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
104
+ return { prompts: PROMPTS };
105
+ });
106
+
107
+ // Get prompt by name
108
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
109
+ return getPrompt(request.params.name);
110
+ });
111
+
67
112
  // Handle tool calls
68
113
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
69
114
  void extra; // suppress unused warning
@@ -78,27 +123,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
78
123
 
79
124
  switch (name) {
80
125
  case 'query_graph':
81
- result = await handleQueryGraph(args as any);
126
+ result = await handleQueryGraph(asArgs<QueryGraphArgs>(args));
82
127
  break;
83
128
 
84
129
  case 'find_calls':
85
- result = await handleFindCalls(args as any);
130
+ result = await handleFindCalls(asArgs<FindCallsArgs>(args));
86
131
  break;
87
132
 
88
133
  case 'find_nodes':
89
- result = await handleFindNodes(args as any);
134
+ result = await handleFindNodes(asArgs<FindNodesArgs>(args));
90
135
  break;
91
136
 
92
137
  case 'trace_alias':
93
- result = await handleTraceAlias(args as any);
138
+ result = await handleTraceAlias(asArgs<TraceAliasArgs>(args));
94
139
  break;
95
140
 
96
141
  case 'trace_dataflow':
97
- result = await handleTraceDataFlow(args as any);
142
+ result = await handleTraceDataFlow(asArgs<TraceDataFlowArgs>(args));
98
143
  break;
99
144
 
100
145
  case 'check_invariant':
101
- result = await handleCheckInvariant(args as any);
146
+ result = await handleCheckInvariant(asArgs<CheckInvariantArgs>(args));
102
147
  break;
103
148
 
104
149
  case 'discover_services':
@@ -107,7 +152,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
107
152
  break;
108
153
 
109
154
  case 'analyze_project':
110
- result = await handleAnalyzeProject(args as any);
155
+ result = await handleAnalyzeProject(asArgs<AnalyzeProjectArgs>(args));
111
156
  break;
112
157
 
113
158
  case 'get_analysis_status':
@@ -119,11 +164,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
119
164
  break;
120
165
 
121
166
  case 'get_schema':
122
- result = await handleGetSchema(args as any);
167
+ result = await handleGetSchema(asArgs<GetSchemaArgs>(args));
123
168
  break;
124
169
 
125
170
  case 'create_guarantee':
126
- result = await handleCreateGuarantee(args as any);
171
+ result = await handleCreateGuarantee(asArgs<CreateGuaranteeArgs>(args));
127
172
  break;
128
173
 
129
174
  case 'list_guarantees':
@@ -131,31 +176,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
131
176
  break;
132
177
 
133
178
  case 'check_guarantees':
134
- result = await handleCheckGuarantees(args as any);
179
+ result = await handleCheckGuarantees(asArgs<CheckGuaranteesArgs>(args));
135
180
  break;
136
181
 
137
182
  case 'delete_guarantee':
138
- result = await handleDeleteGuarantee(args as any);
183
+ result = await handleDeleteGuarantee(asArgs<DeleteGuaranteeArgs>(args));
139
184
  break;
140
185
 
141
186
  case 'get_coverage':
142
- result = await handleGetCoverage(args as any);
187
+ result = await handleGetCoverage(asArgs<GetCoverageArgs>(args));
143
188
  break;
144
189
 
145
190
  case 'get_documentation':
146
- result = await handleGetDocumentation(args as GetDocumentationArgs);
191
+ result = await handleGetDocumentation(asArgs<GetDocumentationArgs>(args));
147
192
  break;
148
193
 
149
194
  case 'find_guards':
150
- result = await handleFindGuards(args as any);
195
+ result = await handleFindGuards(asArgs<FindGuardsArgs>(args));
151
196
  break;
152
197
 
153
198
  case 'report_issue':
154
- result = await handleReportIssue(args as unknown as ReportIssueArgs);
199
+ result = await handleReportIssue(asArgs<ReportIssueArgs>(args));
155
200
  break;
156
201
 
157
202
  case 'get_function_details':
158
- result = await handleGetFunctionDetails(args as unknown as GetFunctionDetailsArgs);
203
+ result = await handleGetFunctionDetails(asArgs<GetFunctionDetailsArgs>(args));
204
+ break;
205
+
206
+ case 'read_project_structure':
207
+ result = await handleReadProjectStructure(asArgs<ReadProjectStructureArgs>(args));
208
+ break;
209
+
210
+ case 'write_config':
211
+ result = await handleWriteConfig(asArgs<WriteConfigArgs>(args));
159
212
  break;
160
213
 
161
214
  default:
@@ -170,8 +223,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
170
223
  return result;
171
224
  } catch (error) {
172
225
  const duration = Date.now() - startTime;
173
- log(`[Grafema MCP] ${name} FAILED after ${duration}ms: ${(error as Error).message}`);
174
- return errorResult((error as Error).message);
226
+ const message = error instanceof Error ? error.message : String(error);
227
+ log(`[Grafema MCP] ✗ ${name} FAILED after ${duration}ms: ${message}`);
228
+ return errorResult(message);
175
229
  }
176
230
  });
177
231