@hasna/microservices 0.0.5 → 0.0.6

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.
@@ -0,0 +1,991 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import {
7
+ createOrg,
8
+ getOrg,
9
+ updateOrg,
10
+ createTeam,
11
+ getTeam,
12
+ listTeams,
13
+ updateTeam,
14
+ deleteTeam,
15
+ getTeamTree,
16
+ getTeamMembers,
17
+ addMember,
18
+ getMember,
19
+ listMembers,
20
+ updateMember,
21
+ removeMember,
22
+ getMembersByRole,
23
+ createCustomer,
24
+ getCustomer,
25
+ listCustomers,
26
+ updateCustomer,
27
+ deleteCustomer,
28
+ searchCustomers,
29
+ mergeCustomers,
30
+ getCustomerByEmail,
31
+ createVendor,
32
+ getVendor,
33
+ listVendors,
34
+ updateVendor,
35
+ deleteVendor,
36
+ searchVendors,
37
+ getVendorsByCategory,
38
+ } from "../db/company.js";
39
+ import {
40
+ logAction,
41
+ searchAudit,
42
+ getAuditStats,
43
+ getAuditTimeline,
44
+ } from "../lib/audit.js";
45
+ import {
46
+ getSetting,
47
+ setSetting,
48
+ getAllSettings,
49
+ deleteSetting,
50
+ } from "../lib/settings.js";
51
+ import {
52
+ generatePnl,
53
+ createPeriod,
54
+ closePeriod,
55
+ listPeriods,
56
+ generateCashflow,
57
+ setBudget,
58
+ getBudgetVsActual,
59
+ listBudgets,
60
+ } from "../lib/finance.js";
61
+
62
+ const server = new McpServer({
63
+ name: "microservice-company",
64
+ version: "0.0.1",
65
+ });
66
+
67
+ // ─── Organization ────────────────────────────────────────────────────────────
68
+
69
+ server.registerTool(
70
+ "create_org",
71
+ {
72
+ title: "Create Organization",
73
+ description: "Create a new organization.",
74
+ inputSchema: {
75
+ name: z.string(),
76
+ legal_name: z.string().optional(),
77
+ tax_id: z.string().optional(),
78
+ address: z.record(z.unknown()).optional(),
79
+ phone: z.string().optional(),
80
+ email: z.string().optional(),
81
+ website: z.string().optional(),
82
+ industry: z.string().optional(),
83
+ currency: z.string().optional(),
84
+ fiscal_year_start: z.string().optional(),
85
+ timezone: z.string().optional(),
86
+ branding: z.record(z.unknown()).optional(),
87
+ settings: z.record(z.unknown()).optional(),
88
+ },
89
+ },
90
+ async (params) => {
91
+ const org = createOrg(params);
92
+ return { content: [{ type: "text", text: JSON.stringify(org, null, 2) }] };
93
+ }
94
+ );
95
+
96
+ server.registerTool(
97
+ "get_org",
98
+ {
99
+ title: "Get Organization",
100
+ description: "Get an organization by ID.",
101
+ inputSchema: { id: z.string() },
102
+ },
103
+ async ({ id }) => {
104
+ const org = getOrg(id);
105
+ if (!org) {
106
+ return { content: [{ type: "text", text: `Organization '${id}' not found.` }], isError: true };
107
+ }
108
+ return { content: [{ type: "text", text: JSON.stringify(org, null, 2) }] };
109
+ }
110
+ );
111
+
112
+ server.registerTool(
113
+ "update_org",
114
+ {
115
+ title: "Update Organization",
116
+ description: "Update an existing organization.",
117
+ inputSchema: {
118
+ id: z.string(),
119
+ name: z.string().optional(),
120
+ legal_name: z.string().optional(),
121
+ tax_id: z.string().optional(),
122
+ address: z.record(z.unknown()).optional(),
123
+ phone: z.string().optional(),
124
+ email: z.string().optional(),
125
+ website: z.string().optional(),
126
+ industry: z.string().optional(),
127
+ currency: z.string().optional(),
128
+ fiscal_year_start: z.string().optional(),
129
+ timezone: z.string().optional(),
130
+ branding: z.record(z.unknown()).optional(),
131
+ settings: z.record(z.unknown()).optional(),
132
+ },
133
+ },
134
+ async ({ id, ...input }) => {
135
+ const org = updateOrg(id, input);
136
+ if (!org) {
137
+ return { content: [{ type: "text", text: `Organization '${id}' not found.` }], isError: true };
138
+ }
139
+ return { content: [{ type: "text", text: JSON.stringify(org, null, 2) }] };
140
+ }
141
+ );
142
+
143
+ // ─── Teams ───────────────────────────────────────────────────────────────────
144
+
145
+ server.registerTool(
146
+ "create_team",
147
+ {
148
+ title: "Create Team",
149
+ description: "Create a new team within an organization.",
150
+ inputSchema: {
151
+ org_id: z.string(),
152
+ name: z.string(),
153
+ parent_id: z.string().optional(),
154
+ department: z.string().optional(),
155
+ cost_center: z.string().optional(),
156
+ metadata: z.record(z.unknown()).optional(),
157
+ },
158
+ },
159
+ async (params) => {
160
+ const team = createTeam(params);
161
+ return { content: [{ type: "text", text: JSON.stringify(team, null, 2) }] };
162
+ }
163
+ );
164
+
165
+ server.registerTool(
166
+ "get_team",
167
+ {
168
+ title: "Get Team",
169
+ description: "Get a team by ID.",
170
+ inputSchema: { id: z.string() },
171
+ },
172
+ async ({ id }) => {
173
+ const team = getTeam(id);
174
+ if (!team) {
175
+ return { content: [{ type: "text", text: `Team '${id}' not found.` }], isError: true };
176
+ }
177
+ return { content: [{ type: "text", text: JSON.stringify(team, null, 2) }] };
178
+ }
179
+ );
180
+
181
+ server.registerTool(
182
+ "list_teams",
183
+ {
184
+ title: "List Teams",
185
+ description: "List teams with optional filters.",
186
+ inputSchema: {
187
+ org_id: z.string().optional(),
188
+ department: z.string().optional(),
189
+ limit: z.number().optional(),
190
+ },
191
+ },
192
+ async (params) => {
193
+ const teams = listTeams(params);
194
+ return {
195
+ content: [{ type: "text", text: JSON.stringify({ teams, count: teams.length }, null, 2) }],
196
+ };
197
+ }
198
+ );
199
+
200
+ server.registerTool(
201
+ "update_team",
202
+ {
203
+ title: "Update Team",
204
+ description: "Update an existing team.",
205
+ inputSchema: {
206
+ id: z.string(),
207
+ name: z.string().optional(),
208
+ parent_id: z.string().optional(),
209
+ department: z.string().optional(),
210
+ cost_center: z.string().optional(),
211
+ metadata: z.record(z.unknown()).optional(),
212
+ },
213
+ },
214
+ async ({ id, ...input }) => {
215
+ const team = updateTeam(id, input);
216
+ if (!team) {
217
+ return { content: [{ type: "text", text: `Team '${id}' not found.` }], isError: true };
218
+ }
219
+ return { content: [{ type: "text", text: JSON.stringify(team, null, 2) }] };
220
+ }
221
+ );
222
+
223
+ server.registerTool(
224
+ "delete_team",
225
+ {
226
+ title: "Delete Team",
227
+ description: "Delete a team by ID.",
228
+ inputSchema: { id: z.string() },
229
+ },
230
+ async ({ id }) => {
231
+ const deleted = deleteTeam(id);
232
+ return { content: [{ type: "text", text: JSON.stringify({ id, deleted }) }] };
233
+ }
234
+ );
235
+
236
+ server.registerTool(
237
+ "get_team_tree",
238
+ {
239
+ title: "Get Team Tree",
240
+ description: "Get the hierarchical team tree for an organization.",
241
+ inputSchema: { org_id: z.string() },
242
+ },
243
+ async ({ org_id }) => {
244
+ const tree = getTeamTree(org_id);
245
+ return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
246
+ }
247
+ );
248
+
249
+ server.registerTool(
250
+ "get_team_members",
251
+ {
252
+ title: "Get Team Members",
253
+ description: "Get all members of a team.",
254
+ inputSchema: { team_id: z.string() },
255
+ },
256
+ async ({ team_id }) => {
257
+ const members = getTeamMembers(team_id);
258
+ return {
259
+ content: [{ type: "text", text: JSON.stringify({ members, count: members.length }, null, 2) }],
260
+ };
261
+ }
262
+ );
263
+
264
+ // ─── Members ─────────────────────────────────────────────────────────────────
265
+
266
+ server.registerTool(
267
+ "add_member",
268
+ {
269
+ title: "Add Member",
270
+ description: "Add a new member to an organization.",
271
+ inputSchema: {
272
+ org_id: z.string(),
273
+ team_id: z.string().optional(),
274
+ name: z.string(),
275
+ email: z.string().optional(),
276
+ role: z.enum(["owner", "admin", "manager", "member", "viewer"]).optional(),
277
+ title: z.string().optional(),
278
+ permissions: z.record(z.unknown()).optional(),
279
+ status: z.string().optional(),
280
+ },
281
+ },
282
+ async (params) => {
283
+ const member = addMember(params);
284
+ return { content: [{ type: "text", text: JSON.stringify(member, null, 2) }] };
285
+ }
286
+ );
287
+
288
+ server.registerTool(
289
+ "get_member",
290
+ {
291
+ title: "Get Member",
292
+ description: "Get a member by ID.",
293
+ inputSchema: { id: z.string() },
294
+ },
295
+ async ({ id }) => {
296
+ const member = getMember(id);
297
+ if (!member) {
298
+ return { content: [{ type: "text", text: `Member '${id}' not found.` }], isError: true };
299
+ }
300
+ return { content: [{ type: "text", text: JSON.stringify(member, null, 2) }] };
301
+ }
302
+ );
303
+
304
+ server.registerTool(
305
+ "list_members",
306
+ {
307
+ title: "List Members",
308
+ description: "List members with optional filters.",
309
+ inputSchema: {
310
+ org_id: z.string().optional(),
311
+ team_id: z.string().optional(),
312
+ role: z.string().optional(),
313
+ status: z.string().optional(),
314
+ limit: z.number().optional(),
315
+ },
316
+ },
317
+ async (params) => {
318
+ const members = listMembers(params);
319
+ return {
320
+ content: [{ type: "text", text: JSON.stringify({ members, count: members.length }, null, 2) }],
321
+ };
322
+ }
323
+ );
324
+
325
+ server.registerTool(
326
+ "update_member",
327
+ {
328
+ title: "Update Member",
329
+ description: "Update an existing member.",
330
+ inputSchema: {
331
+ id: z.string(),
332
+ team_id: z.string().optional(),
333
+ name: z.string().optional(),
334
+ email: z.string().optional(),
335
+ role: z.enum(["owner", "admin", "manager", "member", "viewer"]).optional(),
336
+ title: z.string().optional(),
337
+ permissions: z.record(z.unknown()).optional(),
338
+ status: z.string().optional(),
339
+ },
340
+ },
341
+ async ({ id, ...input }) => {
342
+ const member = updateMember(id, input);
343
+ if (!member) {
344
+ return { content: [{ type: "text", text: `Member '${id}' not found.` }], isError: true };
345
+ }
346
+ return { content: [{ type: "text", text: JSON.stringify(member, null, 2) }] };
347
+ }
348
+ );
349
+
350
+ server.registerTool(
351
+ "remove_member",
352
+ {
353
+ title: "Remove Member",
354
+ description: "Remove a member by ID.",
355
+ inputSchema: { id: z.string() },
356
+ },
357
+ async ({ id }) => {
358
+ const removed = removeMember(id);
359
+ return { content: [{ type: "text", text: JSON.stringify({ id, removed }) }] };
360
+ }
361
+ );
362
+
363
+ server.registerTool(
364
+ "get_members_by_role",
365
+ {
366
+ title: "Get Members by Role",
367
+ description: "Get all members with a specific role in an organization.",
368
+ inputSchema: {
369
+ org_id: z.string(),
370
+ role: z.string(),
371
+ },
372
+ },
373
+ async ({ org_id, role }) => {
374
+ const members = getMembersByRole(org_id, role);
375
+ return {
376
+ content: [{ type: "text", text: JSON.stringify({ members, count: members.length }, null, 2) }],
377
+ };
378
+ }
379
+ );
380
+
381
+ // ─── Customers ───────────────────────────────────────────────────────────────
382
+
383
+ server.registerTool(
384
+ "create_customer",
385
+ {
386
+ title: "Create Customer",
387
+ description: "Create a new customer.",
388
+ inputSchema: {
389
+ org_id: z.string(),
390
+ name: z.string(),
391
+ email: z.string().optional(),
392
+ phone: z.string().optional(),
393
+ company: z.string().optional(),
394
+ address: z.record(z.unknown()).optional(),
395
+ source: z.string().optional(),
396
+ source_ids: z.record(z.unknown()).optional(),
397
+ tags: z.array(z.string()).optional(),
398
+ lifetime_value: z.number().optional(),
399
+ metadata: z.record(z.unknown()).optional(),
400
+ },
401
+ },
402
+ async (params) => {
403
+ const customer = createCustomer(params);
404
+ return { content: [{ type: "text", text: JSON.stringify(customer, null, 2) }] };
405
+ }
406
+ );
407
+
408
+ server.registerTool(
409
+ "get_customer",
410
+ {
411
+ title: "Get Customer",
412
+ description: "Get a customer by ID.",
413
+ inputSchema: { id: z.string() },
414
+ },
415
+ async ({ id }) => {
416
+ const customer = getCustomer(id);
417
+ if (!customer) {
418
+ return { content: [{ type: "text", text: `Customer '${id}' not found.` }], isError: true };
419
+ }
420
+ return { content: [{ type: "text", text: JSON.stringify(customer, null, 2) }] };
421
+ }
422
+ );
423
+
424
+ server.registerTool(
425
+ "list_customers",
426
+ {
427
+ title: "List Customers",
428
+ description: "List customers with optional filters.",
429
+ inputSchema: {
430
+ org_id: z.string().optional(),
431
+ search: z.string().optional(),
432
+ source: z.string().optional(),
433
+ limit: z.number().optional(),
434
+ },
435
+ },
436
+ async (params) => {
437
+ const customers = listCustomers(params);
438
+ return {
439
+ content: [{ type: "text", text: JSON.stringify({ customers, count: customers.length }, null, 2) }],
440
+ };
441
+ }
442
+ );
443
+
444
+ server.registerTool(
445
+ "update_customer",
446
+ {
447
+ title: "Update Customer",
448
+ description: "Update an existing customer.",
449
+ inputSchema: {
450
+ id: z.string(),
451
+ name: z.string().optional(),
452
+ email: z.string().optional(),
453
+ phone: z.string().optional(),
454
+ company: z.string().optional(),
455
+ address: z.record(z.unknown()).optional(),
456
+ source: z.string().optional(),
457
+ source_ids: z.record(z.unknown()).optional(),
458
+ tags: z.array(z.string()).optional(),
459
+ lifetime_value: z.number().optional(),
460
+ metadata: z.record(z.unknown()).optional(),
461
+ },
462
+ },
463
+ async ({ id, ...input }) => {
464
+ const customer = updateCustomer(id, input);
465
+ if (!customer) {
466
+ return { content: [{ type: "text", text: `Customer '${id}' not found.` }], isError: true };
467
+ }
468
+ return { content: [{ type: "text", text: JSON.stringify(customer, null, 2) }] };
469
+ }
470
+ );
471
+
472
+ server.registerTool(
473
+ "delete_customer",
474
+ {
475
+ title: "Delete Customer",
476
+ description: "Delete a customer by ID.",
477
+ inputSchema: { id: z.string() },
478
+ },
479
+ async ({ id }) => {
480
+ const deleted = deleteCustomer(id);
481
+ return { content: [{ type: "text", text: JSON.stringify({ id, deleted }) }] };
482
+ }
483
+ );
484
+
485
+ server.registerTool(
486
+ "search_customers",
487
+ {
488
+ title: "Search Customers",
489
+ description: "Search customers by name, email, phone, or company.",
490
+ inputSchema: {
491
+ org_id: z.string(),
492
+ query: z.string(),
493
+ },
494
+ },
495
+ async ({ org_id, query }) => {
496
+ const results = searchCustomers(org_id, query);
497
+ return {
498
+ content: [{ type: "text", text: JSON.stringify({ results, count: results.length }, null, 2) }],
499
+ };
500
+ }
501
+ );
502
+
503
+ server.registerTool(
504
+ "merge_customers",
505
+ {
506
+ title: "Merge Customers",
507
+ description: "Merge two customers — keep the first, merge data from second, delete second.",
508
+ inputSchema: {
509
+ id1: z.string(),
510
+ id2: z.string(),
511
+ },
512
+ },
513
+ async ({ id1, id2 }) => {
514
+ const merged = mergeCustomers(id1, id2);
515
+ if (!merged) {
516
+ return { content: [{ type: "text", text: "One or both customers not found." }], isError: true };
517
+ }
518
+ return { content: [{ type: "text", text: JSON.stringify(merged, null, 2) }] };
519
+ }
520
+ );
521
+
522
+ server.registerTool(
523
+ "get_customer_by_email",
524
+ {
525
+ title: "Get Customer by Email",
526
+ description: "Find a customer by email within an organization.",
527
+ inputSchema: {
528
+ org_id: z.string(),
529
+ email: z.string(),
530
+ },
531
+ },
532
+ async ({ org_id, email }) => {
533
+ const customer = getCustomerByEmail(org_id, email);
534
+ if (!customer) {
535
+ return { content: [{ type: "text", text: `No customer with email '${email}' found.` }], isError: true };
536
+ }
537
+ return { content: [{ type: "text", text: JSON.stringify(customer, null, 2) }] };
538
+ }
539
+ );
540
+
541
+ // ─── Vendors ─────────────────────────────────────────────────────────────────
542
+
543
+ server.registerTool(
544
+ "create_vendor",
545
+ {
546
+ title: "Create Vendor",
547
+ description: "Create a new vendor.",
548
+ inputSchema: {
549
+ org_id: z.string(),
550
+ name: z.string(),
551
+ email: z.string().optional(),
552
+ phone: z.string().optional(),
553
+ company: z.string().optional(),
554
+ category: z.enum(["supplier", "contractor", "partner", "agency"]).optional(),
555
+ payment_terms: z.string().optional(),
556
+ address: z.record(z.unknown()).optional(),
557
+ metadata: z.record(z.unknown()).optional(),
558
+ },
559
+ },
560
+ async (params) => {
561
+ const vendor = createVendor(params);
562
+ return { content: [{ type: "text", text: JSON.stringify(vendor, null, 2) }] };
563
+ }
564
+ );
565
+
566
+ server.registerTool(
567
+ "get_vendor",
568
+ {
569
+ title: "Get Vendor",
570
+ description: "Get a vendor by ID.",
571
+ inputSchema: { id: z.string() },
572
+ },
573
+ async ({ id }) => {
574
+ const vendor = getVendor(id);
575
+ if (!vendor) {
576
+ return { content: [{ type: "text", text: `Vendor '${id}' not found.` }], isError: true };
577
+ }
578
+ return { content: [{ type: "text", text: JSON.stringify(vendor, null, 2) }] };
579
+ }
580
+ );
581
+
582
+ server.registerTool(
583
+ "list_vendors",
584
+ {
585
+ title: "List Vendors",
586
+ description: "List vendors with optional filters.",
587
+ inputSchema: {
588
+ org_id: z.string().optional(),
589
+ category: z.string().optional(),
590
+ search: z.string().optional(),
591
+ limit: z.number().optional(),
592
+ },
593
+ },
594
+ async (params) => {
595
+ const vendors = listVendors(params);
596
+ return {
597
+ content: [{ type: "text", text: JSON.stringify({ vendors, count: vendors.length }, null, 2) }],
598
+ };
599
+ }
600
+ );
601
+
602
+ server.registerTool(
603
+ "update_vendor",
604
+ {
605
+ title: "Update Vendor",
606
+ description: "Update an existing vendor.",
607
+ inputSchema: {
608
+ id: z.string(),
609
+ name: z.string().optional(),
610
+ email: z.string().optional(),
611
+ phone: z.string().optional(),
612
+ company: z.string().optional(),
613
+ category: z.enum(["supplier", "contractor", "partner", "agency"]).optional(),
614
+ payment_terms: z.string().optional(),
615
+ address: z.record(z.unknown()).optional(),
616
+ metadata: z.record(z.unknown()).optional(),
617
+ },
618
+ },
619
+ async ({ id, ...input }) => {
620
+ const vendor = updateVendor(id, input);
621
+ if (!vendor) {
622
+ return { content: [{ type: "text", text: `Vendor '${id}' not found.` }], isError: true };
623
+ }
624
+ return { content: [{ type: "text", text: JSON.stringify(vendor, null, 2) }] };
625
+ }
626
+ );
627
+
628
+ server.registerTool(
629
+ "delete_vendor",
630
+ {
631
+ title: "Delete Vendor",
632
+ description: "Delete a vendor by ID.",
633
+ inputSchema: { id: z.string() },
634
+ },
635
+ async ({ id }) => {
636
+ const deleted = deleteVendor(id);
637
+ return { content: [{ type: "text", text: JSON.stringify({ id, deleted }) }] };
638
+ }
639
+ );
640
+
641
+ server.registerTool(
642
+ "search_vendors",
643
+ {
644
+ title: "Search Vendors",
645
+ description: "Search vendors by name, email, phone, or company.",
646
+ inputSchema: {
647
+ org_id: z.string(),
648
+ query: z.string(),
649
+ },
650
+ },
651
+ async ({ org_id, query }) => {
652
+ const results = searchVendors(org_id, query);
653
+ return {
654
+ content: [{ type: "text", text: JSON.stringify({ results, count: results.length }, null, 2) }],
655
+ };
656
+ }
657
+ );
658
+
659
+ server.registerTool(
660
+ "get_vendors_by_category",
661
+ {
662
+ title: "Get Vendors by Category",
663
+ description: "Get all vendors in a specific category within an organization.",
664
+ inputSchema: {
665
+ org_id: z.string(),
666
+ category: z.string(),
667
+ },
668
+ },
669
+ async ({ org_id, category }) => {
670
+ const vendors = getVendorsByCategory(org_id, category);
671
+ return {
672
+ content: [{ type: "text", text: JSON.stringify({ vendors, count: vendors.length }, null, 2) }],
673
+ };
674
+ }
675
+ );
676
+
677
+ // ─── Audit ───────────────────────────────────────────────────────────────────
678
+
679
+ server.registerTool(
680
+ "search_audit",
681
+ {
682
+ title: "Search Audit Log",
683
+ description: "Search audit log entries with filters.",
684
+ inputSchema: {
685
+ org_id: z.string().optional(),
686
+ actor: z.string().optional(),
687
+ service: z.string().optional(),
688
+ action: z.enum(["create", "update", "delete", "execute", "login", "approve"]).optional(),
689
+ entity_type: z.string().optional(),
690
+ entity_id: z.string().optional(),
691
+ from: z.string().optional(),
692
+ to: z.string().optional(),
693
+ limit: z.number().optional(),
694
+ },
695
+ },
696
+ async (params) => {
697
+ const results = searchAudit(params);
698
+ return {
699
+ content: [{ type: "text", text: JSON.stringify({ entries: results, count: results.length }, null, 2) }],
700
+ };
701
+ }
702
+ );
703
+
704
+ server.registerTool(
705
+ "log_audit",
706
+ {
707
+ title: "Log Audit Action",
708
+ description: "Log an action to the audit trail.",
709
+ inputSchema: {
710
+ org_id: z.string().optional(),
711
+ actor: z.string(),
712
+ action: z.enum(["create", "update", "delete", "execute", "login", "approve"]),
713
+ service: z.string().optional(),
714
+ entity_type: z.string().optional(),
715
+ entity_id: z.string().optional(),
716
+ details: z.record(z.unknown()).optional(),
717
+ },
718
+ },
719
+ async (params) => {
720
+ const entry = logAction(params);
721
+ return { content: [{ type: "text", text: JSON.stringify(entry, null, 2) }] };
722
+ }
723
+ );
724
+
725
+ server.registerTool(
726
+ "audit_stats",
727
+ {
728
+ title: "Audit Statistics",
729
+ description: "Get audit log statistics — counts by actor, service, and action.",
730
+ inputSchema: {
731
+ org_id: z.string().optional(),
732
+ from: z.string().optional(),
733
+ to: z.string().optional(),
734
+ },
735
+ },
736
+ async ({ org_id, from, to }) => {
737
+ const stats = getAuditStats(org_id, from, to);
738
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
739
+ }
740
+ );
741
+
742
+ server.registerTool(
743
+ "audit_timeline",
744
+ {
745
+ title: "Audit Timeline",
746
+ description: "Get the full audit history for a specific entity.",
747
+ inputSchema: {
748
+ entity_type: z.string(),
749
+ entity_id: z.string(),
750
+ },
751
+ },
752
+ async ({ entity_type, entity_id }) => {
753
+ const timeline = getAuditTimeline(entity_type, entity_id);
754
+ return {
755
+ content: [{ type: "text", text: JSON.stringify({ timeline, count: timeline.length }, null, 2) }],
756
+ };
757
+ }
758
+ );
759
+
760
+ // ─── Settings ────────────────────────────────────────────────────────────────
761
+
762
+ server.registerTool(
763
+ "get_setting",
764
+ {
765
+ title: "Get Setting",
766
+ description: "Get a single setting by key.",
767
+ inputSchema: {
768
+ org_id: z.string().nullable(),
769
+ key: z.string(),
770
+ },
771
+ },
772
+ async ({ org_id, key }) => {
773
+ const setting = getSetting(org_id, key);
774
+ if (!setting) {
775
+ return { content: [{ type: "text", text: `Setting '${key}' not found.` }], isError: true };
776
+ }
777
+ return { content: [{ type: "text", text: JSON.stringify(setting, null, 2) }] };
778
+ }
779
+ );
780
+
781
+ server.registerTool(
782
+ "set_setting",
783
+ {
784
+ title: "Set Setting",
785
+ description: "Set a setting value (upsert).",
786
+ inputSchema: {
787
+ org_id: z.string().nullable(),
788
+ key: z.string(),
789
+ value: z.string(),
790
+ category: z.string().optional(),
791
+ },
792
+ },
793
+ async ({ org_id, key, value, category }) => {
794
+ const setting = setSetting(org_id, key, value, category);
795
+ return { content: [{ type: "text", text: JSON.stringify(setting, null, 2) }] };
796
+ }
797
+ );
798
+
799
+ server.registerTool(
800
+ "list_settings",
801
+ {
802
+ title: "List Settings",
803
+ description: "List all settings for an organization, optionally filtered by category.",
804
+ inputSchema: {
805
+ org_id: z.string().nullable(),
806
+ category: z.string().optional(),
807
+ },
808
+ },
809
+ async ({ org_id, category }) => {
810
+ const settings = getAllSettings(org_id, category);
811
+ return {
812
+ content: [{ type: "text", text: JSON.stringify({ settings, count: settings.length }, null, 2) }],
813
+ };
814
+ }
815
+ );
816
+
817
+ server.registerTool(
818
+ "delete_setting",
819
+ {
820
+ title: "Delete Setting",
821
+ description: "Delete a setting by key.",
822
+ inputSchema: {
823
+ org_id: z.string().nullable(),
824
+ key: z.string(),
825
+ },
826
+ },
827
+ async ({ org_id, key }) => {
828
+ const deleted = deleteSetting(org_id, key);
829
+ return { content: [{ type: "text", text: JSON.stringify({ key, deleted }) }] };
830
+ }
831
+ );
832
+
833
+ // ─── Financial Consolidation ─────────────────────────────────────────────────
834
+
835
+ server.registerTool(
836
+ "generate_pnl",
837
+ {
838
+ title: "Generate P&L Report",
839
+ description: "Generate a Profit & Loss report from closed financial periods.",
840
+ inputSchema: {
841
+ org_id: z.string(),
842
+ start_date: z.string(),
843
+ end_date: z.string(),
844
+ },
845
+ },
846
+ async ({ org_id, start_date, end_date }) => {
847
+ const report = generatePnl(org_id, start_date, end_date);
848
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
849
+ }
850
+ );
851
+
852
+ server.registerTool(
853
+ "create_period",
854
+ {
855
+ title: "Create Financial Period",
856
+ description: "Create a new financial period (month, quarter, or year).",
857
+ inputSchema: {
858
+ org_id: z.string(),
859
+ name: z.string(),
860
+ type: z.enum(["month", "quarter", "year"]),
861
+ start_date: z.string(),
862
+ end_date: z.string(),
863
+ },
864
+ },
865
+ async ({ org_id, name, type, start_date, end_date }) => {
866
+ const period = createPeriod(org_id, name, type, start_date, end_date);
867
+ return { content: [{ type: "text", text: JSON.stringify(period, null, 2) }] };
868
+ }
869
+ );
870
+
871
+ server.registerTool(
872
+ "close_period",
873
+ {
874
+ title: "Close Financial Period",
875
+ description: "Close a financial period with final revenue and expense figures.",
876
+ inputSchema: {
877
+ period_id: z.string(),
878
+ revenue: z.number(),
879
+ expenses: z.number(),
880
+ },
881
+ },
882
+ async ({ period_id, revenue, expenses }) => {
883
+ const period = closePeriod(period_id, revenue, expenses);
884
+ if (!period) {
885
+ return { content: [{ type: "text", text: `Period '${period_id}' not found.` }], isError: true };
886
+ }
887
+ return { content: [{ type: "text", text: JSON.stringify(period, null, 2) }] };
888
+ }
889
+ );
890
+
891
+ server.registerTool(
892
+ "list_periods",
893
+ {
894
+ title: "List Financial Periods",
895
+ description: "List financial periods for an organization, optionally filtered by type.",
896
+ inputSchema: {
897
+ org_id: z.string(),
898
+ type: z.enum(["month", "quarter", "year"]).optional(),
899
+ },
900
+ },
901
+ async ({ org_id, type }) => {
902
+ const periods = listPeriods(org_id, type);
903
+ return {
904
+ content: [{ type: "text", text: JSON.stringify({ periods, count: periods.length }, null, 2) }],
905
+ };
906
+ }
907
+ );
908
+
909
+ server.registerTool(
910
+ "generate_cashflow",
911
+ {
912
+ title: "Generate Cashflow Report",
913
+ description: "Generate a cashflow report from financial periods.",
914
+ inputSchema: {
915
+ org_id: z.string(),
916
+ start_date: z.string(),
917
+ end_date: z.string(),
918
+ },
919
+ },
920
+ async ({ org_id, start_date, end_date }) => {
921
+ const report = generateCashflow(org_id, start_date, end_date);
922
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
923
+ }
924
+ );
925
+
926
+ server.registerTool(
927
+ "set_budget",
928
+ {
929
+ title: "Set Budget",
930
+ description: "Set or update a department's monthly budget.",
931
+ inputSchema: {
932
+ org_id: z.string(),
933
+ department: z.string(),
934
+ monthly_amount: z.number(),
935
+ },
936
+ },
937
+ async ({ org_id, department, monthly_amount }) => {
938
+ const budget = setBudget(org_id, department, monthly_amount);
939
+ return { content: [{ type: "text", text: JSON.stringify(budget, null, 2) }] };
940
+ }
941
+ );
942
+
943
+ server.registerTool(
944
+ "budget_status",
945
+ {
946
+ title: "Budget vs Actual",
947
+ description: "Compare a department's budget against actual spending for a given month.",
948
+ inputSchema: {
949
+ org_id: z.string(),
950
+ department: z.string(),
951
+ month: z.string().describe("Month in YYYY-MM format"),
952
+ },
953
+ },
954
+ async ({ org_id, department, month }) => {
955
+ const result = getBudgetVsActual(org_id, department, month);
956
+ if (!result) {
957
+ return { content: [{ type: "text", text: `No budget found for department '${department}'.` }], isError: true };
958
+ }
959
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
960
+ }
961
+ );
962
+
963
+ server.registerTool(
964
+ "list_budgets",
965
+ {
966
+ title: "List Budgets",
967
+ description: "List all budgets for an organization.",
968
+ inputSchema: {
969
+ org_id: z.string(),
970
+ },
971
+ },
972
+ async ({ org_id }) => {
973
+ const budgets = listBudgets(org_id);
974
+ return {
975
+ content: [{ type: "text", text: JSON.stringify({ budgets, count: budgets.length }, null, 2) }],
976
+ };
977
+ }
978
+ );
979
+
980
+ // ─── Start ───────────────────────────────────────────────────────────────────
981
+
982
+ async function main() {
983
+ const transport = new StdioServerTransport();
984
+ await server.connect(transport);
985
+ console.error("microservice-company MCP server running on stdio");
986
+ }
987
+
988
+ main().catch((error) => {
989
+ console.error("Fatal error:", error);
990
+ process.exit(1);
991
+ });