@mtaap/mcp 0.1.1 → 0.1.2

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,1678 @@
1
+ import {
2
+ dist_exports,
3
+ prisma
4
+ } from "./chunk-IEQQVLM4.js";
5
+ import {
6
+ AbandonTaskInputSchema,
7
+ AddNoteInputSchema,
8
+ ApiKeyPermission,
9
+ AssignTaskInputSchema,
10
+ CompleteTaskInputSchema,
11
+ CreatePersonalProjectInputSchema,
12
+ GetProjectContextInputSchema,
13
+ GetTaskInputSchema,
14
+ ListProjectsInputSchema,
15
+ ListTasksInputSchema,
16
+ ProjectOrigin,
17
+ ProjectType,
18
+ ReportErrorInputSchema,
19
+ TaskState,
20
+ UpdateProgressInputSchema,
21
+ UserRole,
22
+ z
23
+ } from "./chunk-ASNGTDTC.js";
24
+
25
+ // src/index.ts
26
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
28
+
29
+ // ../../packages/auth/dist/nextauth.js
30
+ import Credentials from "next-auth/providers/credentials";
31
+
32
+ // ../../packages/auth/dist/ldap.js
33
+ async function authenticateWithLdap(email, password, organizationId) {
34
+ const settings = await prisma.organizationSettings.findUnique({
35
+ where: { organizationId }
36
+ });
37
+ if (!settings?.ldapEnabled || !settings.ldapUrl) {
38
+ return null;
39
+ }
40
+ const config = {
41
+ url: settings.ldapUrl,
42
+ bindDN: settings.ldapBindDN || "",
43
+ searchBase: settings.ldapSearchBase || ""
44
+ };
45
+ return performLdapAuth(email, password, config);
46
+ }
47
+ async function performLdapAuth(email, password, config) {
48
+ const ldapjs = await import("ldapjs");
49
+ return new Promise((resolve) => {
50
+ const client = ldapjs.createClient({
51
+ url: config.url,
52
+ timeout: 5e3,
53
+ connectTimeout: 1e4
54
+ });
55
+ client.on("error", (err) => {
56
+ console.error("LDAP connection error:", err.message);
57
+ resolve(null);
58
+ });
59
+ const usernameAttr = "mail";
60
+ const searchFilter = `(${usernameAttr}=${ldapEscape(email)})`;
61
+ const searchOptions = {
62
+ filter: searchFilter,
63
+ scope: "sub",
64
+ attributes: ["dn", "mail", "cn", "displayName", "givenName", "sn"]
65
+ };
66
+ if (config.bindDN) {
67
+ client.bind(config.bindDN, "", (bindErr) => {
68
+ if (bindErr) {
69
+ console.error("LDAP service bind failed:", bindErr.message);
70
+ client.unbind();
71
+ resolve(null);
72
+ return;
73
+ }
74
+ searchAndBind(client, config.searchBase, searchOptions, password, resolve);
75
+ });
76
+ } else {
77
+ searchAndBind(client, config.searchBase, searchOptions, password, resolve);
78
+ }
79
+ });
80
+ }
81
+ function searchAndBind(client, searchBase, searchOptions, password, resolve) {
82
+ client.search(searchBase, searchOptions, (searchErr, searchRes) => {
83
+ if (searchErr) {
84
+ console.error("LDAP search error:", searchErr.message);
85
+ client.unbind();
86
+ resolve(null);
87
+ return;
88
+ }
89
+ let userEntry = null;
90
+ searchRes.on("searchEntry", (entry) => {
91
+ const dn = entry.dn.toString();
92
+ const attrs = entry.attributes;
93
+ const mail = getAttrValue(attrs, "mail");
94
+ const displayName = getAttrValue(attrs, "displayName");
95
+ const cn = getAttrValue(attrs, "cn");
96
+ const givenName = getAttrValue(attrs, "givenName");
97
+ const sn = getAttrValue(attrs, "sn");
98
+ const name = displayName || cn || [givenName, sn].filter(Boolean).join(" ") || mail;
99
+ userEntry = {
100
+ email: mail || "",
101
+ name: name || "",
102
+ dn
103
+ };
104
+ });
105
+ searchRes.on("error", (err) => {
106
+ console.error("LDAP search result error:", err.message);
107
+ client.unbind();
108
+ resolve(null);
109
+ });
110
+ searchRes.on("end", () => {
111
+ if (!userEntry) {
112
+ client.unbind();
113
+ resolve(null);
114
+ return;
115
+ }
116
+ const userDn = userEntry.dn;
117
+ const foundUser = userEntry;
118
+ client.bind(userDn, password, (authErr) => {
119
+ client.unbind();
120
+ if (authErr) {
121
+ console.error("LDAP user bind failed:", authErr.message);
122
+ resolve(null);
123
+ return;
124
+ }
125
+ resolve(foundUser);
126
+ });
127
+ });
128
+ });
129
+ }
130
+ function getAttrValue(attrs, name) {
131
+ const attr = attrs.find((a) => a.type.toLowerCase() === name.toLowerCase());
132
+ if (!attr || !attr.values || attr.values.length === 0) {
133
+ return void 0;
134
+ }
135
+ const val = attr.values[0];
136
+ if (typeof val === "string")
137
+ return val;
138
+ if (val && typeof val === "object" && "toString" in val) {
139
+ return val.toString();
140
+ }
141
+ return void 0;
142
+ }
143
+ function ldapEscape(str) {
144
+ return str.replace(/[\\*()]/g, (char) => `\\${char.charCodeAt(0).toString(16)}`);
145
+ }
146
+ async function getOrganizationLdapConfig(organizationSlug) {
147
+ const org = await prisma.organization.findUnique({
148
+ where: { slug: organizationSlug },
149
+ include: { settings: true }
150
+ });
151
+ if (!org) {
152
+ return null;
153
+ }
154
+ return {
155
+ enabled: org.settings?.ldapEnabled ?? false,
156
+ organizationId: org.id
157
+ };
158
+ }
159
+ async function findOrCreateLdapUser(ldapUser, organizationId) {
160
+ let user = await prisma.user.findUnique({
161
+ where: { email: ldapUser.email }
162
+ });
163
+ if (!user) {
164
+ const crypto = await import("crypto");
165
+ const randomPassword = crypto.randomBytes(32).toString("hex");
166
+ const bcrypt = await import("bcryptjs");
167
+ const passwordHash = await bcrypt.hash(randomPassword, 10);
168
+ user = await prisma.user.create({
169
+ data: {
170
+ email: ldapUser.email,
171
+ name: ldapUser.name,
172
+ passwordHash,
173
+ role: UserRole.MEMBER
174
+ }
175
+ });
176
+ }
177
+ const membership = await prisma.organizationUser.findFirst({
178
+ where: {
179
+ userId: user.id,
180
+ organizationId
181
+ }
182
+ });
183
+ if (!membership) {
184
+ const subscription = await prisma.subscription.findUnique({
185
+ where: { organizationId }
186
+ });
187
+ if (subscription && subscription.seats > 0) {
188
+ const currentSeats = await prisma.organizationUser.count({
189
+ where: { organizationId }
190
+ });
191
+ if (currentSeats >= subscription.seats) {
192
+ throw new Error("No available seats in organization subscription");
193
+ }
194
+ }
195
+ await prisma.organizationUser.create({
196
+ data: {
197
+ userId: user.id,
198
+ organizationId,
199
+ roleId: UserRole.MEMBER
200
+ }
201
+ });
202
+ }
203
+ return {
204
+ id: user.id,
205
+ email: user.email,
206
+ name: user.name
207
+ };
208
+ }
209
+
210
+ // ../../packages/auth/dist/nextauth.js
211
+ var THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;
212
+ var authConfig = {
213
+ providers: [
214
+ Credentials({
215
+ name: "credentials",
216
+ credentials: {
217
+ email: { label: "Email", type: "email" },
218
+ password: { label: "Password", type: "password" },
219
+ organizationSlug: { label: "Organization", type: "text" }
220
+ },
221
+ async authorize(credentials) {
222
+ const parsed = z.object({
223
+ email: z.string().email(),
224
+ password: z.string().min(8),
225
+ organizationSlug: z.string().optional()
226
+ }).safeParse(credentials);
227
+ if (!parsed.success) {
228
+ return null;
229
+ }
230
+ const { email, password, organizationSlug } = parsed.data;
231
+ if (organizationSlug) {
232
+ const ldapConfig = await getOrganizationLdapConfig(organizationSlug);
233
+ if (ldapConfig?.enabled) {
234
+ const ldapUser = await authenticateWithLdap(email, password, ldapConfig.organizationId);
235
+ if (ldapUser) {
236
+ const user2 = await findOrCreateLdapUser(ldapUser, ldapConfig.organizationId);
237
+ return {
238
+ id: user2.id,
239
+ email: user2.email,
240
+ name: user2.name,
241
+ role: UserRole.MEMBER,
242
+ organizationId: ldapConfig.organizationId
243
+ };
244
+ }
245
+ return null;
246
+ }
247
+ }
248
+ const whereClause = {};
249
+ if (organizationSlug) {
250
+ whereClause.organization = {
251
+ slug: organizationSlug
252
+ };
253
+ }
254
+ const user = await prisma.user.findUnique({
255
+ where: { email },
256
+ include: {
257
+ organizations: {
258
+ where: whereClause,
259
+ take: 1
260
+ }
261
+ }
262
+ });
263
+ if (!user) {
264
+ return null;
265
+ }
266
+ const bcrypt = await import("bcryptjs");
267
+ const isValid = await bcrypt.compare(password, user.passwordHash);
268
+ if (!isValid) {
269
+ return null;
270
+ }
271
+ const orgUser = user.organizations?.[0];
272
+ const role = orgUser ? orgUser.roleId : UserRole.MEMBER;
273
+ return {
274
+ id: user.id,
275
+ email: user.email,
276
+ name: user.name,
277
+ role,
278
+ organizationId: orgUser?.organizationId
279
+ };
280
+ }
281
+ })
282
+ ],
283
+ session: {
284
+ strategy: "jwt",
285
+ maxAge: THIRTY_DAYS_IN_SECONDS
286
+ },
287
+ pages: {
288
+ signIn: "/auth/signin",
289
+ error: "/auth/error"
290
+ },
291
+ callbacks: {
292
+ async jwt({ token, user }) {
293
+ if (user) {
294
+ token.id = user.id;
295
+ token.role = user.role;
296
+ token.organizationId = user.organizationId;
297
+ }
298
+ return token;
299
+ },
300
+ async session({ session, token }) {
301
+ if (token) {
302
+ session.user = {
303
+ ...session.user,
304
+ id: token.id,
305
+ role: token.role,
306
+ organizationId: token.organizationId
307
+ };
308
+ }
309
+ return session;
310
+ }
311
+ }
312
+ };
313
+ async function validateApiKey(apiKey) {
314
+ const keyHash = await hashApiKey(apiKey);
315
+ const keyRecord = await prisma.apiKey.findFirst({
316
+ where: {
317
+ keyHash,
318
+ revoked: false
319
+ },
320
+ include: {
321
+ user: {
322
+ include: {
323
+ organizations: {
324
+ take: 1,
325
+ include: {
326
+ organization: true
327
+ }
328
+ }
329
+ }
330
+ }
331
+ }
332
+ });
333
+ if (!keyRecord || keyRecord.expiresAt && keyRecord.expiresAt < /* @__PURE__ */ new Date()) {
334
+ return null;
335
+ }
336
+ await prisma.apiKey.update({
337
+ where: { id: keyRecord.id },
338
+ data: { lastUsedAt: /* @__PURE__ */ new Date() }
339
+ });
340
+ const orgUser = keyRecord.user.organizations?.[0];
341
+ return {
342
+ user: keyRecord.user,
343
+ organization: orgUser?.organization,
344
+ apiKey: keyRecord
345
+ };
346
+ }
347
+ var API_KEY_PERMISSION_RANK = {
348
+ [ApiKeyPermission.READ]: 1,
349
+ [ApiKeyPermission.WRITE]: 2,
350
+ [ApiKeyPermission.ADMIN]: 3
351
+ };
352
+ async function hashApiKey(apiKey) {
353
+ const crypto = await import("crypto");
354
+ return crypto.createHash("sha256").update(apiKey).digest("hex");
355
+ }
356
+
357
+ // ../../packages/auth/dist/stateMachine.js
358
+ var VALID_TRANSITIONS = {
359
+ [TaskState.BACKLOG]: [TaskState.READY],
360
+ [TaskState.READY]: [TaskState.IN_PROGRESS],
361
+ [TaskState.IN_PROGRESS]: [TaskState.REVIEW],
362
+ [TaskState.REVIEW]: [TaskState.DONE, TaskState.IN_PROGRESS],
363
+ [TaskState.DONE]: []
364
+ };
365
+
366
+ // src/version.ts
367
+ var VERSION = "0.1.2";
368
+
369
+ // ../../packages/git/dist/github.js
370
+ import { Octokit } from "octokit";
371
+ var GitHubProvider = class {
372
+ octokit;
373
+ constructor(token) {
374
+ this.octokit = new Octokit({ auth: token });
375
+ }
376
+ async createBranch(options) {
377
+ try {
378
+ const { data: ref } = await this.octokit.rest.git.getRef({
379
+ owner: options.owner,
380
+ repo: options.repo,
381
+ ref: `heads/${options.sourceBranch}`
382
+ });
383
+ await this.octokit.rest.git.createRef({
384
+ owner: options.owner,
385
+ repo: options.repo,
386
+ ref: `refs/heads/${options.newBranch}`,
387
+ sha: ref.object.sha
388
+ });
389
+ return {
390
+ success: true,
391
+ branchName: options.newBranch
392
+ };
393
+ } catch (error) {
394
+ return {
395
+ success: false,
396
+ branchName: options.newBranch,
397
+ error: error.message || "Failed to create branch"
398
+ };
399
+ }
400
+ }
401
+ async createPR(options) {
402
+ try {
403
+ const { data: pr } = await this.octokit.rest.pulls.create({
404
+ owner: options.owner,
405
+ repo: options.repo,
406
+ title: options.title,
407
+ body: options.body,
408
+ head: options.head,
409
+ base: options.base
410
+ });
411
+ return {
412
+ success: true,
413
+ prUrl: pr.html_url,
414
+ prNumber: pr.number
415
+ };
416
+ } catch (error) {
417
+ return {
418
+ success: false,
419
+ prUrl: "",
420
+ prNumber: 0,
421
+ error: error.message || "Failed to create pull request"
422
+ };
423
+ }
424
+ }
425
+ async getBranchInfo(options) {
426
+ try {
427
+ const { data: ref } = await this.octokit.rest.git.getRef({
428
+ owner: options.owner,
429
+ repo: options.repo,
430
+ ref: `heads/${options.branch}`
431
+ });
432
+ return ref.object.sha;
433
+ } catch {
434
+ return null;
435
+ }
436
+ }
437
+ async listBranches(owner, repo) {
438
+ try {
439
+ const { data: branches } = await this.octokit.rest.repos.listBranches({
440
+ owner,
441
+ repo,
442
+ per_page: 100
443
+ });
444
+ return branches.map((branch) => ({
445
+ name: branch.name,
446
+ commitSha: branch.commit.sha,
447
+ protected: branch.protected
448
+ }));
449
+ } catch (error) {
450
+ console.error("Failed to list branches:", error);
451
+ return [];
452
+ }
453
+ }
454
+ async setupWebhook(options) {
455
+ try {
456
+ const { data: webhook } = await this.octokit.rest.repos.createWebhook({
457
+ owner: options.owner,
458
+ repo: options.repo,
459
+ name: "web",
460
+ config: {
461
+ url: options.url,
462
+ content_type: "json",
463
+ secret: options.secret
464
+ },
465
+ events: options.events || ["push", "pull_request"],
466
+ active: true
467
+ });
468
+ return {
469
+ success: true,
470
+ webhookId: webhook.id
471
+ };
472
+ } catch (error) {
473
+ return {
474
+ success: false,
475
+ webhookId: 0,
476
+ error: error.message || "Failed to setup webhook"
477
+ };
478
+ }
479
+ }
480
+ async getPRInfo(prNumber, owner, repo) {
481
+ try {
482
+ const { data: pr } = await this.octokit.rest.pulls.get({
483
+ owner,
484
+ repo,
485
+ pull_number: prNumber
486
+ });
487
+ return {
488
+ number: pr.number,
489
+ title: pr.title,
490
+ state: pr.state,
491
+ htmlUrl: pr.html_url,
492
+ mergedAt: pr.merged_at ? new Date(pr.merged_at) : void 0,
493
+ user: pr.user ? {
494
+ login: pr.user.login
495
+ } : void 0
496
+ };
497
+ } catch {
498
+ return null;
499
+ }
500
+ }
501
+ async getRecentCommits(owner, repo, branch, limit = 10) {
502
+ try {
503
+ const { data: commits } = await this.octokit.rest.repos.listCommits({
504
+ owner,
505
+ repo,
506
+ sha: branch,
507
+ per_page: limit
508
+ });
509
+ return commits.map((commit) => ({
510
+ sha: commit.sha,
511
+ message: commit.commit.message,
512
+ author: commit.commit.author?.name && commit.commit.author?.email ? {
513
+ name: commit.commit.author.name,
514
+ email: commit.commit.author.email
515
+ } : void 0,
516
+ date: new Date(commit.commit.author?.date || Date.now())
517
+ }));
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+ async deleteBranch(owner, repo, branch) {
523
+ try {
524
+ await this.octokit.rest.git.deleteRef({
525
+ owner,
526
+ repo,
527
+ ref: `heads/${branch}`
528
+ });
529
+ return true;
530
+ } catch (error) {
531
+ console.error(`Failed to delete branch ${branch}:`, error.message);
532
+ return false;
533
+ }
534
+ }
535
+ async getRepositoryReadme(owner, repo) {
536
+ try {
537
+ const { data } = await this.octokit.rest.repos.getReadme({
538
+ owner,
539
+ repo,
540
+ mediaType: {
541
+ format: "raw"
542
+ }
543
+ });
544
+ return data;
545
+ } catch {
546
+ return null;
547
+ }
548
+ }
549
+ async getRepositoryPackageJson(owner, repo) {
550
+ try {
551
+ const { data } = await this.octokit.rest.repos.getContent({
552
+ owner,
553
+ repo,
554
+ path: "package.json"
555
+ });
556
+ if ("content" in data && data.type === "file") {
557
+ return Buffer.from(data.content, "base64").toString("utf-8");
558
+ }
559
+ return null;
560
+ } catch {
561
+ return null;
562
+ }
563
+ }
564
+ static parseRepositoryUrl(url) {
565
+ try {
566
+ const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
567
+ if (!match || match.length < 3) {
568
+ return null;
569
+ }
570
+ return {
571
+ owner: match[1],
572
+ repo: match[2].replace(/\.git$/, "")
573
+ };
574
+ } catch {
575
+ return null;
576
+ }
577
+ }
578
+ static async verifyWebhookSignature(payload, signature, secret) {
579
+ const crypto = await import("crypto");
580
+ const expectedSignature = crypto.createHmac("sha256", secret).update(payload).digest("hex");
581
+ const parts = signature.split("=");
582
+ if (parts.length < 2) {
583
+ return false;
584
+ }
585
+ const receivedSignature = parts[1];
586
+ return crypto.timingSafeEqual(Buffer.from(receivedSignature), Buffer.from(expectedSignature));
587
+ }
588
+ async verifyRepositoryAccess(owner, repo) {
589
+ try {
590
+ await this.octokit.rest.repos.get({
591
+ owner,
592
+ repo
593
+ });
594
+ return { success: true };
595
+ } catch (error) {
596
+ if (error.status === 404) {
597
+ return {
598
+ success: false,
599
+ error: "Repository not found or you don't have access"
600
+ };
601
+ }
602
+ if (error.status === 401 || error.status === 403) {
603
+ return {
604
+ success: false,
605
+ error: "Authentication failed. Check your GitHub token permissions"
606
+ };
607
+ }
608
+ return {
609
+ success: false,
610
+ error: error.message || "Failed to verify repository access"
611
+ };
612
+ }
613
+ }
614
+ };
615
+
616
+ // ../../packages/git/dist/errors.js
617
+ var GitErrorType;
618
+ (function(GitErrorType2) {
619
+ GitErrorType2["BRANCH_NOT_FOUND"] = "BRANCH_NOT_FOUND";
620
+ GitErrorType2["BRANCH_EXISTS"] = "BRANCH_EXISTS";
621
+ GitErrorType2["AUTH_INVALID"] = "AUTH_INVALID";
622
+ GitErrorType2["REPOSITORY_NOT_FOUND"] = "REPOSITORY_NOT_FOUND";
623
+ GitErrorType2["RATE_LIMITED"] = "RATE_LIMITED";
624
+ GitErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
625
+ GitErrorType2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
626
+ GitErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
627
+ })(GitErrorType || (GitErrorType = {}));
628
+
629
+ // src/permissions.ts
630
+ var PERMISSION_RANK = {
631
+ READ: 1,
632
+ WRITE: 2,
633
+ ADMIN: 3
634
+ };
635
+ function assertApiKeyPermission(apiKey, required, toolName) {
636
+ const actualRank = PERMISSION_RANK[apiKey.permissions] ?? 0;
637
+ const requiredRank = PERMISSION_RANK[required] ?? 0;
638
+ if (actualRank >= requiredRank) {
639
+ return;
640
+ }
641
+ console.warn("API key permission violation", {
642
+ keyId: apiKey.id,
643
+ requiredPermission: required,
644
+ actualPermission: apiKey.permissions,
645
+ tool: toolName
646
+ });
647
+ const error = new Error(
648
+ `API key lacks required permissions (required: ${required})`
649
+ );
650
+ error.status = 403;
651
+ throw error;
652
+ }
653
+
654
+ // src/index.ts
655
+ async function getAuthenticatedUser(apiKey) {
656
+ const result = await validateApiKey(apiKey);
657
+ if (!result) {
658
+ throw new Error("Invalid API key");
659
+ }
660
+ return {
661
+ userId: result.user.id,
662
+ organizationId: result.organization?.id,
663
+ apiKey: result.apiKey
664
+ };
665
+ }
666
+ async function ensureAccessControl(projectId, userId, organizationId) {
667
+ if (!organizationId) {
668
+ const project = await prisma.project.findUnique({
669
+ where: { id: projectId },
670
+ include: { owner: true }
671
+ });
672
+ if (project?.ownerId !== userId) {
673
+ throw new Error(
674
+ "Access denied: You do not have permission to access this project"
675
+ );
676
+ }
677
+ } else {
678
+ const project = await prisma.project.findUnique({
679
+ where: { id: projectId },
680
+ include: { organization: true }
681
+ });
682
+ if (!project || project.organizationId !== organizationId) {
683
+ throw new Error(
684
+ "Access denied: You do not have permission to access this project"
685
+ );
686
+ }
687
+ const userTags = await prisma.userTag.findMany({
688
+ where: { userId },
689
+ include: { tag: true }
690
+ });
691
+ const projectTags = await prisma.projectTag.findMany({
692
+ where: { projectId },
693
+ include: { tag: true }
694
+ });
695
+ const userTagNames = new Set(
696
+ userTags.map((ut) => ut.tag.name)
697
+ );
698
+ const projectTagNames = new Set(
699
+ projectTags.map((pt) => pt.tag.name)
700
+ );
701
+ const hasAccess = [...userTagNames].some((tag) => projectTagNames.has(tag));
702
+ if (!hasAccess) {
703
+ throw new Error(
704
+ "Access denied: Your tags do not grant access to this project"
705
+ );
706
+ }
707
+ }
708
+ }
709
+ function getGitHubClient(_organizationId) {
710
+ const githubToken = process.env.GITHUB_TOKEN;
711
+ if (!githubToken) {
712
+ throw new Error("GitHub token not configured");
713
+ }
714
+ return new GitHubProvider(githubToken);
715
+ }
716
+ function slugify(text, maxLength = 50) {
717
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, maxLength);
718
+ }
719
+ async function createMCPServer() {
720
+ const server = new McpServer({
721
+ name: "mtaap",
722
+ version: VERSION
723
+ });
724
+ const apiKey = process.env.MTAAP_API_KEY;
725
+ if (!apiKey) {
726
+ throw new Error("MTAAP_API_KEY environment variable is required");
727
+ }
728
+ const auth = await getAuthenticatedUser(apiKey);
729
+ server.registerTool(
730
+ "list_projects",
731
+ {
732
+ description: "List accessible projects (personal + team via tags)"
733
+ },
734
+ async (args) => {
735
+ assertApiKeyPermission(
736
+ auth.apiKey,
737
+ ApiKeyPermission.READ,
738
+ "list_projects"
739
+ );
740
+ const validated = ListProjectsInputSchema.parse(args);
741
+ const projects = await listProjects(
742
+ auth.userId,
743
+ auth.organizationId,
744
+ validated.workspaceType
745
+ );
746
+ return {
747
+ content: [
748
+ {
749
+ type: "text",
750
+ text: JSON.stringify(projects, null, 2)
751
+ }
752
+ ]
753
+ };
754
+ }
755
+ );
756
+ server.registerTool(
757
+ "list_tasks",
758
+ {
759
+ description: "Returns available tasks (filterable by project, state)"
760
+ },
761
+ async (args) => {
762
+ assertApiKeyPermission(auth.apiKey, ApiKeyPermission.READ, "list_tasks");
763
+ const validated = ListTasksInputSchema.parse(args);
764
+ if (validated.projectId) {
765
+ await ensureAccessControl(
766
+ validated.projectId,
767
+ auth.userId,
768
+ auth.organizationId
769
+ );
770
+ }
771
+ const tasks = await listTasks(
772
+ auth.userId,
773
+ auth.organizationId,
774
+ validated.projectId,
775
+ validated.state,
776
+ validated.assigneeId
777
+ );
778
+ return {
779
+ content: [
780
+ {
781
+ type: "text",
782
+ text: JSON.stringify(tasks, null, 2)
783
+ }
784
+ ]
785
+ };
786
+ }
787
+ );
788
+ server.registerTool(
789
+ "get_task",
790
+ {
791
+ description: "Full task details including acceptance criteria"
792
+ },
793
+ async (args) => {
794
+ assertApiKeyPermission(auth.apiKey, ApiKeyPermission.READ, "get_task");
795
+ const validated = GetTaskInputSchema.parse(args);
796
+ const task = await getTaskWithChecks(
797
+ validated.taskId,
798
+ auth.userId,
799
+ auth.organizationId
800
+ );
801
+ return {
802
+ content: [
803
+ {
804
+ type: "text",
805
+ text: JSON.stringify(task, null, 2)
806
+ }
807
+ ]
808
+ };
809
+ }
810
+ );
811
+ server.registerTool(
812
+ "assign_task",
813
+ {
814
+ description: "Atomic claim \u2192 creates branch. Fails if already taken."
815
+ },
816
+ async (args) => {
817
+ assertApiKeyPermission(
818
+ auth.apiKey,
819
+ ApiKeyPermission.WRITE,
820
+ "assign_task"
821
+ );
822
+ const validated = AssignTaskInputSchema.parse(args);
823
+ const result = await assignTask(
824
+ validated.projectId,
825
+ validated.taskId,
826
+ auth.userId,
827
+ auth.organizationId,
828
+ validated.expectedState
829
+ );
830
+ return {
831
+ content: [
832
+ {
833
+ type: "text",
834
+ text: JSON.stringify(result, null, 2)
835
+ }
836
+ ]
837
+ };
838
+ }
839
+ );
840
+ server.registerTool(
841
+ "update_progress",
842
+ {
843
+ description: "Reports status, updates checkboxes, writes checkpoint"
844
+ },
845
+ async (args) => {
846
+ assertApiKeyPermission(
847
+ auth.apiKey,
848
+ ApiKeyPermission.WRITE,
849
+ "update_progress"
850
+ );
851
+ const validated = UpdateProgressInputSchema.parse(args);
852
+ const result = await updateProgress(
853
+ validated.taskId,
854
+ auth.userId,
855
+ validated.statusMessage,
856
+ validated.completedCheckpointIds,
857
+ validated.currentCheckpointIndex
858
+ );
859
+ return {
860
+ content: [
861
+ {
862
+ type: "text",
863
+ text: JSON.stringify(result, null, 2)
864
+ }
865
+ ]
866
+ };
867
+ }
868
+ );
869
+ server.registerTool(
870
+ "complete_task",
871
+ {
872
+ description: "Marks complete, triggers PR, deletes local state file"
873
+ },
874
+ async (args) => {
875
+ assertApiKeyPermission(
876
+ auth.apiKey,
877
+ ApiKeyPermission.WRITE,
878
+ "complete_task"
879
+ );
880
+ const validated = CompleteTaskInputSchema.parse(args);
881
+ const result = await completeTask(
882
+ validated.projectId,
883
+ validated.taskId,
884
+ auth.userId,
885
+ auth.organizationId,
886
+ validated.pullRequestTitle,
887
+ validated.pullRequestBody
888
+ );
889
+ return {
890
+ content: [
891
+ {
892
+ type: "text",
893
+ text: JSON.stringify(result, null, 2)
894
+ }
895
+ ]
896
+ };
897
+ }
898
+ );
899
+ server.registerTool(
900
+ "check_active_task",
901
+ {
902
+ description: "Check for resumable task in .mtaap/active-task.json"
903
+ },
904
+ async () => {
905
+ assertApiKeyPermission(
906
+ auth.apiKey,
907
+ ApiKeyPermission.READ,
908
+ "check_active_task"
909
+ );
910
+ const result = await checkActiveTask();
911
+ return {
912
+ content: [
913
+ {
914
+ type: "text",
915
+ text: JSON.stringify(result, null, 2)
916
+ }
917
+ ]
918
+ };
919
+ }
920
+ );
921
+ server.registerTool(
922
+ "report_error",
923
+ {
924
+ description: "Report unrecoverable error, displays on task in webapp"
925
+ },
926
+ async (args) => {
927
+ assertApiKeyPermission(
928
+ auth.apiKey,
929
+ ApiKeyPermission.WRITE,
930
+ "report_error"
931
+ );
932
+ const validated = ReportErrorInputSchema.parse(args);
933
+ const result = await reportTaskError(
934
+ validated.taskId,
935
+ auth.userId,
936
+ validated.errorType,
937
+ validated.errorMessage,
938
+ validated.context
939
+ );
940
+ return {
941
+ content: [
942
+ {
943
+ type: "text",
944
+ text: JSON.stringify(result, null, 2)
945
+ }
946
+ ]
947
+ };
948
+ }
949
+ );
950
+ server.registerTool(
951
+ "get_project_context",
952
+ {
953
+ description: "Returns assembled context (README, stack, conventions)"
954
+ },
955
+ async (args) => {
956
+ assertApiKeyPermission(
957
+ auth.apiKey,
958
+ ApiKeyPermission.READ,
959
+ "get_project_context"
960
+ );
961
+ const validated = GetProjectContextInputSchema.parse(args);
962
+ await ensureAccessControl(
963
+ validated.projectId,
964
+ auth.userId,
965
+ auth.organizationId
966
+ );
967
+ const context = await getProjectContext(validated.projectId);
968
+ return {
969
+ content: [
970
+ {
971
+ type: "text",
972
+ text: JSON.stringify(context, null, 2)
973
+ }
974
+ ]
975
+ };
976
+ }
977
+ );
978
+ server.registerTool(
979
+ "add_note",
980
+ {
981
+ description: "Append implementation notes to task"
982
+ },
983
+ async (args) => {
984
+ assertApiKeyPermission(auth.apiKey, ApiKeyPermission.WRITE, "add_note");
985
+ const validated = AddNoteInputSchema.parse(args);
986
+ const result = await addTaskNote(
987
+ validated.taskId,
988
+ auth.userId,
989
+ validated.content
990
+ );
991
+ return {
992
+ content: [
993
+ {
994
+ type: "text",
995
+ text: JSON.stringify(result, null, 2)
996
+ }
997
+ ]
998
+ };
999
+ }
1000
+ );
1001
+ server.registerTool(
1002
+ "abandon_task",
1003
+ {
1004
+ description: "Unassign from a task and optionally delete the branch"
1005
+ },
1006
+ async (args) => {
1007
+ assertApiKeyPermission(
1008
+ auth.apiKey,
1009
+ ApiKeyPermission.WRITE,
1010
+ "abandon_task"
1011
+ );
1012
+ const validated = AbandonTaskInputSchema.parse(args);
1013
+ await ensureAccessControl(
1014
+ validated.projectId,
1015
+ auth.userId,
1016
+ auth.organizationId || null
1017
+ );
1018
+ const result = await abandonTask(
1019
+ validated.taskId,
1020
+ validated.projectId,
1021
+ auth.userId,
1022
+ validated.deleteBranch
1023
+ );
1024
+ return {
1025
+ content: [
1026
+ {
1027
+ type: "text",
1028
+ text: JSON.stringify(result, null, 2)
1029
+ }
1030
+ ]
1031
+ };
1032
+ }
1033
+ );
1034
+ server.registerTool(
1035
+ "create_personal_project",
1036
+ {
1037
+ description: "Create project in user's personal workspace"
1038
+ },
1039
+ async (args) => {
1040
+ assertApiKeyPermission(
1041
+ auth.apiKey,
1042
+ ApiKeyPermission.WRITE,
1043
+ "create_personal_project"
1044
+ );
1045
+ const validated = CreatePersonalProjectInputSchema.parse(args);
1046
+ const result = await createPersonalProject(
1047
+ auth.userId,
1048
+ validated.name,
1049
+ validated.description,
1050
+ validated.repositoryUrl
1051
+ );
1052
+ return {
1053
+ content: [
1054
+ {
1055
+ type: "text",
1056
+ text: JSON.stringify(result, null, 2)
1057
+ }
1058
+ ]
1059
+ };
1060
+ }
1061
+ );
1062
+ server.registerTool(
1063
+ "get_version",
1064
+ {
1065
+ description: "Get the current MTAAP version"
1066
+ },
1067
+ async () => {
1068
+ assertApiKeyPermission(auth.apiKey, ApiKeyPermission.READ, "get_version");
1069
+ return {
1070
+ content: [
1071
+ {
1072
+ type: "text",
1073
+ text: JSON.stringify(
1074
+ {
1075
+ version: VERSION,
1076
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1077
+ },
1078
+ null,
1079
+ 2
1080
+ )
1081
+ }
1082
+ ]
1083
+ };
1084
+ }
1085
+ );
1086
+ const transport = new StdioServerTransport();
1087
+ await server.connect(transport);
1088
+ }
1089
+ async function listProjects(userId, organizationId, workspaceType = "ALL") {
1090
+ const where = {};
1091
+ const include = {};
1092
+ if (workspaceType === "PERSONAL" || workspaceType === "ALL") {
1093
+ where.ownerId = userId;
1094
+ include.owner = true;
1095
+ }
1096
+ if (organizationId && (workspaceType === "TEAM" || workspaceType === "ALL")) {
1097
+ where.OR = [
1098
+ { organizationId },
1099
+ { type: ProjectType.PERSONAL, ownerId: userId }
1100
+ ];
1101
+ include.organization = true;
1102
+ include.projectTags = {
1103
+ include: { tag: true }
1104
+ };
1105
+ }
1106
+ const projects = await prisma.project.findMany({
1107
+ where,
1108
+ include,
1109
+ orderBy: { updatedAt: "desc" }
1110
+ });
1111
+ const formattedProjects = projects.map(
1112
+ (project) => ({
1113
+ id: project.id,
1114
+ name: project.name,
1115
+ description: project.description,
1116
+ type: project.type,
1117
+ origin: project.origin,
1118
+ repositoryUrl: project.repositoryUrl,
1119
+ baseBranch: project.baseBranch,
1120
+ tags: project.projectTags?.map((pt) => pt.tag.name) || [],
1121
+ createdAt: project.createdAt,
1122
+ updatedAt: project.updatedAt
1123
+ })
1124
+ );
1125
+ return formattedProjects;
1126
+ }
1127
+ async function listTasks(userId, organizationId, projectId, state, assigneeId) {
1128
+ const where = { projectId };
1129
+ if (organizationId) {
1130
+ where.project = {
1131
+ organizationId
1132
+ };
1133
+ }
1134
+ if (state) {
1135
+ where.state = state;
1136
+ }
1137
+ if (assigneeId) {
1138
+ where.assigneeId = assigneeId;
1139
+ }
1140
+ const tasks = await prisma.task.findMany({
1141
+ where,
1142
+ include: {
1143
+ project: true,
1144
+ assignee: true,
1145
+ creator: true,
1146
+ epic: true
1147
+ },
1148
+ orderBy: { createdAt: "desc" }
1149
+ });
1150
+ const formattedTasks = await Promise.all(
1151
+ tasks.map(async (task) => {
1152
+ const acceptanceCriteria = await prisma.acceptanceCriterion.findMany({
1153
+ where: { taskId: task.id },
1154
+ orderBy: { order: "asc" }
1155
+ });
1156
+ return {
1157
+ id: task.id,
1158
+ projectId: task.projectId,
1159
+ projectName: task.project?.name || "Unknown",
1160
+ epicId: task.epicId,
1161
+ epicName: task.epic?.name || null,
1162
+ title: task.title,
1163
+ description: task.description,
1164
+ state: task.state,
1165
+ priority: task.priority,
1166
+ assigneeId: task.assigneeId,
1167
+ assigneeName: task.assignee?.name || null,
1168
+ assigneeEmail: task.assignee?.email || null,
1169
+ createdBy: task.createdBy,
1170
+ createdByName: task.creator?.name || null,
1171
+ assignedAt: task.assignedAt,
1172
+ startedAt: task.startedAt,
1173
+ completedAt: task.completedAt,
1174
+ branchName: task.branchName,
1175
+ pullRequestUrl: task.pullRequestUrl,
1176
+ pullRequestNumber: task.pullRequestNumber,
1177
+ errorType: task.errorType,
1178
+ errorMessage: task.errorMessage,
1179
+ acceptanceCriteria: acceptanceCriteria.map((ac) => ({
1180
+ id: ac.id,
1181
+ description: ac.description,
1182
+ completed: ac.completed,
1183
+ completedAt: ac.completedAt,
1184
+ order: ac.order
1185
+ })),
1186
+ createdAt: task.createdAt,
1187
+ updatedAt: task.updatedAt
1188
+ };
1189
+ })
1190
+ );
1191
+ return formattedTasks;
1192
+ }
1193
+ async function getTaskWithChecks(taskId, userId, organizationId) {
1194
+ const task = await prisma.task.findUnique({
1195
+ where: { id: taskId },
1196
+ include: {
1197
+ project: {
1198
+ include: {
1199
+ organization: true,
1200
+ projectTags: {
1201
+ include: { tag: true }
1202
+ }
1203
+ }
1204
+ },
1205
+ epic: true,
1206
+ assignee: true,
1207
+ creator: true,
1208
+ acceptanceCriteria: {
1209
+ orderBy: { order: "asc" }
1210
+ },
1211
+ progressUpdates: {
1212
+ orderBy: { createdAt: "desc" },
1213
+ take: 10
1214
+ },
1215
+ notes: {
1216
+ orderBy: { createdAt: "desc" },
1217
+ take: 10
1218
+ }
1219
+ }
1220
+ });
1221
+ if (!task) {
1222
+ throw new Error("Task not found");
1223
+ }
1224
+ if (organizationId && task.project?.organizationId !== organizationId) {
1225
+ const userTags = await prisma.userTag.findMany({
1226
+ where: { userId },
1227
+ include: { tag: true }
1228
+ });
1229
+ const projectTagNames = new Set(
1230
+ task.project.projectTags?.map(
1231
+ (pt) => pt.tag.name
1232
+ ) || []
1233
+ );
1234
+ const userTagNames = new Set(
1235
+ userTags.map((ut) => ut.tag.name)
1236
+ );
1237
+ const hasAccess = [...userTagNames].some((tag) => projectTagNames.has(tag));
1238
+ if (!hasAccess) {
1239
+ throw new Error(
1240
+ "Access denied: Your tags do not grant access to this project"
1241
+ );
1242
+ }
1243
+ }
1244
+ const formattedTask = {
1245
+ id: task.id,
1246
+ projectId: task.projectId,
1247
+ projectName: task.project?.name || "Unknown",
1248
+ epicId: task.epicId,
1249
+ epicName: task.epic?.name || null,
1250
+ title: task.title,
1251
+ description: task.description,
1252
+ state: task.state,
1253
+ priority: task.priority,
1254
+ assigneeId: task.assigneeId,
1255
+ assigneeName: task.assignee?.name || null,
1256
+ assigneeEmail: task.assignee?.email || null,
1257
+ createdBy: task.createdBy,
1258
+ createdByName: task.creator?.name || null,
1259
+ assignedAt: task.assignedAt,
1260
+ startedAt: task.startedAt,
1261
+ completedAt: task.completedAt,
1262
+ branchName: task.branchName,
1263
+ pullRequestUrl: task.pullRequestUrl,
1264
+ pullRequestNumber: task.pullRequestNumber,
1265
+ errorType: task.errorType,
1266
+ errorMessage: task.errorMessage,
1267
+ acceptanceCriteria: task.acceptanceCriteria.map(
1268
+ (ac) => ({
1269
+ id: ac.id,
1270
+ description: ac.description,
1271
+ completed: ac.completed,
1272
+ completedAt: ac.completedAt,
1273
+ order: ac.order
1274
+ })
1275
+ ),
1276
+ progressUpdates: task.progressUpdates.map(
1277
+ (pu) => ({
1278
+ id: pu.id,
1279
+ message: pu.message,
1280
+ checkpoints: pu.checkpoints,
1281
+ userId: pu.userId,
1282
+ createdAt: pu.createdAt
1283
+ })
1284
+ ),
1285
+ notes: task.notes.map((note) => ({
1286
+ id: note.id,
1287
+ content: note.content,
1288
+ userId: note.userId,
1289
+ createdAt: note.createdAt
1290
+ })),
1291
+ createdAt: task.createdAt,
1292
+ updatedAt: task.updatedAt
1293
+ };
1294
+ return formattedTask;
1295
+ }
1296
+ async function assignTask(projectId, taskId, userId, organizationId, expectedState = TaskState.READY) {
1297
+ await ensureAccessControl(projectId, userId, organizationId);
1298
+ const task = await prisma.task.findUnique({
1299
+ where: { id: taskId },
1300
+ include: { project: true }
1301
+ });
1302
+ if (!task) {
1303
+ throw new Error("Task not found");
1304
+ }
1305
+ if (task.state !== expectedState) {
1306
+ return {
1307
+ success: false,
1308
+ taskId,
1309
+ currentState: task.state,
1310
+ message: `Task is already in state: ${task.state}`
1311
+ };
1312
+ }
1313
+ const repoInfo = GitHubProvider.parseRepositoryUrl(
1314
+ task.project?.repositoryUrl || ""
1315
+ );
1316
+ if (!repoInfo) {
1317
+ throw new Error("Invalid repository URL");
1318
+ }
1319
+ const github = getGitHubClient(task.project?.organizationId || "");
1320
+ const branchSuffix = slugify(task.title);
1321
+ const branchName = `feature/${taskId}-${branchSuffix}`;
1322
+ const createResult = await github.createBranch({
1323
+ owner: repoInfo.owner,
1324
+ repo: repoInfo.repo,
1325
+ sourceBranch: task.project?.baseBranch || "develop",
1326
+ newBranch: branchName
1327
+ });
1328
+ if (!createResult.success) {
1329
+ throw new Error(`Failed to create branch: ${createResult.error}`);
1330
+ }
1331
+ const updatedTask = await prisma.task.update({
1332
+ where: { id: taskId },
1333
+ data: {
1334
+ state: TaskState.IN_PROGRESS,
1335
+ assigneeId: userId,
1336
+ assignedAt: /* @__PURE__ */ new Date(),
1337
+ branchName,
1338
+ startedAt: /* @__PURE__ */ new Date()
1339
+ }
1340
+ });
1341
+ return {
1342
+ success: true,
1343
+ taskId: updatedTask.id,
1344
+ branchName
1345
+ };
1346
+ }
1347
+ async function updateProgress(taskId, userId, statusMessage, completedCheckpointIds, _currentCheckpointIndex) {
1348
+ const task = await prisma.task.findUnique({
1349
+ where: { id: taskId }
1350
+ });
1351
+ if (!task) {
1352
+ throw new Error("Task not found");
1353
+ }
1354
+ if (task.assigneeId !== userId) {
1355
+ throw new Error("You are not assigned to this task");
1356
+ }
1357
+ if (completedCheckpointIds && completedCheckpointIds.length > 0) {
1358
+ await prisma.acceptanceCriterion.updateMany({
1359
+ where: {
1360
+ id: { in: completedCheckpointIds }
1361
+ },
1362
+ data: {
1363
+ completed: true,
1364
+ completedAt: /* @__PURE__ */ new Date()
1365
+ }
1366
+ });
1367
+ }
1368
+ if (statusMessage) {
1369
+ await prisma.progressUpdate.create({
1370
+ data: {
1371
+ taskId,
1372
+ userId,
1373
+ message: statusMessage,
1374
+ checkpoints: []
1375
+ }
1376
+ });
1377
+ }
1378
+ const updatedTask = await prisma.task.update({
1379
+ where: { id: taskId },
1380
+ data: {
1381
+ updatedAt: /* @__PURE__ */ new Date()
1382
+ }
1383
+ });
1384
+ return {
1385
+ success: true,
1386
+ taskId: updatedTask.id
1387
+ };
1388
+ }
1389
+ async function completeTask(projectId, taskId, userId, organizationId, pullRequestTitle, pullRequestBody) {
1390
+ await ensureAccessControl(projectId, userId, organizationId);
1391
+ const task = await prisma.task.findUnique({
1392
+ where: { id: taskId },
1393
+ include: { project: true }
1394
+ });
1395
+ if (!task) {
1396
+ throw new Error("Task not found");
1397
+ }
1398
+ if (task.assigneeId !== userId) {
1399
+ throw new Error("You are not assigned to this task");
1400
+ }
1401
+ if (task.state !== TaskState.IN_PROGRESS) {
1402
+ throw new Error("Task must be in progress to be completed");
1403
+ }
1404
+ if (!task.branchName) {
1405
+ throw new Error("Task has no associated branch");
1406
+ }
1407
+ const repoInfo = GitHubProvider.parseRepositoryUrl(
1408
+ task.project?.repositoryUrl || ""
1409
+ );
1410
+ if (!repoInfo) {
1411
+ throw new Error("Invalid repository URL");
1412
+ }
1413
+ const github = getGitHubClient(task.project?.organizationId || "");
1414
+ const acceptanceCriteria = await prisma.acceptanceCriterion.findMany({
1415
+ where: { taskId },
1416
+ orderBy: { order: "asc" }
1417
+ });
1418
+ const acceptanceChecklist = acceptanceCriteria.map(
1419
+ (ac) => `- [${ac.completed ? "x" : " "}] ${ac.description}`
1420
+ ).join("\n");
1421
+ const prTitle = pullRequestTitle || `[${taskId}] ${task.title}`;
1422
+ const prBody = pullRequestBody || `${task.description}
1423
+
1424
+ ### Acceptance Criteria
1425
+
1426
+ ${acceptanceChecklist}`;
1427
+ const createResult = await github.createPR({
1428
+ owner: repoInfo.owner,
1429
+ repo: repoInfo.repo,
1430
+ title: prTitle,
1431
+ body: prBody,
1432
+ head: task.branchName,
1433
+ base: task.project?.baseBranch || "develop"
1434
+ });
1435
+ if (!createResult.success) {
1436
+ throw new Error(`Failed to create PR: ${createResult.error}`);
1437
+ }
1438
+ const updatedTask = await prisma.task.update({
1439
+ where: { id: taskId },
1440
+ data: {
1441
+ state: TaskState.REVIEW,
1442
+ completedAt: /* @__PURE__ */ new Date(),
1443
+ pullRequestUrl: createResult.prUrl,
1444
+ pullRequestNumber: createResult.prNumber
1445
+ }
1446
+ });
1447
+ return {
1448
+ success: true,
1449
+ taskId: updatedTask.id,
1450
+ prUrl: createResult.prUrl,
1451
+ prNumber: createResult.prNumber
1452
+ };
1453
+ }
1454
+ async function checkActiveTask() {
1455
+ const fs = await import("fs");
1456
+ const path = await import("path");
1457
+ const activeTaskPath = path.join(process.cwd(), ".mtaap", "active-task.json");
1458
+ if (!await fs.promises.access(activeTaskPath).catch(() => false)) {
1459
+ return {
1460
+ hasActiveTask: false,
1461
+ task: null
1462
+ };
1463
+ }
1464
+ const content = await fs.promises.readFile(activeTaskPath, "utf-8");
1465
+ const activeTask = JSON.parse(content);
1466
+ return {
1467
+ hasActiveTask: true,
1468
+ task: activeTask
1469
+ };
1470
+ }
1471
+ async function reportTaskError(taskId, userId, errorType, errorMessage, _context) {
1472
+ const task = await prisma.task.findUnique({
1473
+ where: { id: taskId }
1474
+ });
1475
+ if (!task) {
1476
+ throw new Error("Task not found");
1477
+ }
1478
+ if (task.assigneeId !== userId) {
1479
+ throw new Error("You are not assigned to this task");
1480
+ }
1481
+ const updatedTask = await prisma.task.update({
1482
+ where: { id: taskId },
1483
+ data: {
1484
+ errorType,
1485
+ errorMessage
1486
+ }
1487
+ });
1488
+ return {
1489
+ success: true,
1490
+ taskId: updatedTask.id
1491
+ };
1492
+ }
1493
+ async function getProjectContext(projectId) {
1494
+ const project = await prisma.project.findUnique({
1495
+ where: { id: projectId },
1496
+ include: {
1497
+ organization: {
1498
+ include: { settings: true }
1499
+ }
1500
+ }
1501
+ });
1502
+ if (!project) {
1503
+ throw new Error("Project not found");
1504
+ }
1505
+ const repoInfo = GitHubProvider.parseRepositoryUrl(project.repositoryUrl);
1506
+ let readme = "";
1507
+ let stack = [];
1508
+ if (repoInfo) {
1509
+ const github = getGitHubClient(project.organizationId || "");
1510
+ const readmeResult = await github.getRepositoryReadme(
1511
+ repoInfo.owner,
1512
+ repoInfo.repo
1513
+ );
1514
+ readme = readmeResult || "No README found";
1515
+ const packageJsonResult = await github.getRepositoryPackageJson(
1516
+ repoInfo.owner,
1517
+ repoInfo.repo
1518
+ );
1519
+ if (packageJsonResult) {
1520
+ const packageJson = JSON.parse(packageJsonResult);
1521
+ const deps = {
1522
+ ...packageJson.dependencies,
1523
+ ...packageJson.devDependencies
1524
+ };
1525
+ stack = Object.keys(deps).slice(0, 20);
1526
+ }
1527
+ }
1528
+ const recentCompletedTasks = await prisma.task.findMany({
1529
+ where: {
1530
+ project: {
1531
+ id: projectId
1532
+ },
1533
+ state: TaskState.DONE
1534
+ },
1535
+ orderBy: { completedAt: "desc" },
1536
+ take: 10,
1537
+ include: {
1538
+ creator: {
1539
+ select: { id: true, name: true }
1540
+ }
1541
+ }
1542
+ });
1543
+ const conventions = {
1544
+ branchPrefix: "feature/",
1545
+ commitFormat: project.organization?.settings?.enforceConventionalCommits ? "conventional" : "none",
1546
+ testCommand: "npm test",
1547
+ baseBranch: project.baseBranch || "develop",
1548
+ notes: project.organization?.settings?.maxPersonalProjectsPerUser?.toString() || ""
1549
+ };
1550
+ return {
1551
+ readme: readme.substring(0, 2e3),
1552
+ stack,
1553
+ recentCompleted: recentCompletedTasks.map(
1554
+ (task) => ({
1555
+ id: task.id,
1556
+ title: task.title,
1557
+ completedAt: task.completedAt
1558
+ })
1559
+ ),
1560
+ conventions
1561
+ };
1562
+ }
1563
+ async function addTaskNote(taskId, userId, content) {
1564
+ const task = await prisma.task.findUnique({
1565
+ where: { id: taskId }
1566
+ });
1567
+ if (!task) {
1568
+ throw new Error("Task not found");
1569
+ }
1570
+ if (task.assigneeId !== userId) {
1571
+ throw new Error("You are not assigned to this task");
1572
+ }
1573
+ const note = await prisma.taskNote.create({
1574
+ data: {
1575
+ taskId,
1576
+ userId,
1577
+ content
1578
+ }
1579
+ });
1580
+ return {
1581
+ success: true,
1582
+ noteId: note.id
1583
+ };
1584
+ }
1585
+ async function abandonTask(taskId, projectId, userId, deleteBranch) {
1586
+ const task = await prisma.task.findUnique({
1587
+ where: { id: taskId },
1588
+ include: { project: true }
1589
+ });
1590
+ if (!task) {
1591
+ throw new Error("Task not found");
1592
+ }
1593
+ if (task.projectId !== projectId) {
1594
+ throw new Error("Task does not belong to the specified project");
1595
+ }
1596
+ if (task.assigneeId !== userId) {
1597
+ throw new Error("You can only abandon tasks you are assigned to");
1598
+ }
1599
+ const repositoryUrl = task.project?.repositoryUrl;
1600
+ const repoInfo = repositoryUrl ? GitHubProvider.parseRepositoryUrl(repositoryUrl) : void 0;
1601
+ if (deleteBranch && repoInfo && task.branchName) {
1602
+ const github = getGitHubClient(task.project?.organizationId || "");
1603
+ try {
1604
+ await github.deleteBranch(repoInfo.owner, repoInfo.repo, task.branchName);
1605
+ } catch (error) {
1606
+ console.error("Failed to delete GitHub branch when abandoning task", {
1607
+ taskId,
1608
+ projectId: task.projectId,
1609
+ organizationId: task.project?.organizationId,
1610
+ repositoryOwner: repoInfo.owner,
1611
+ repositoryName: repoInfo.repo,
1612
+ branchName: task.branchName,
1613
+ error
1614
+ });
1615
+ throw new Error("Failed to delete branch");
1616
+ }
1617
+ }
1618
+ const newState = task.state === TaskState.IN_PROGRESS ? TaskState.READY : task.state;
1619
+ const updatedTask = await prisma.task.update({
1620
+ where: { id: taskId },
1621
+ data: {
1622
+ state: newState,
1623
+ assigneeId: null,
1624
+ assignedAt: null,
1625
+ startedAt: null,
1626
+ updatedAt: /* @__PURE__ */ new Date()
1627
+ }
1628
+ });
1629
+ return {
1630
+ success: true,
1631
+ taskId: updatedTask.id,
1632
+ state: updatedTask.state,
1633
+ branchDeleted: deleteBranch ? !!repoInfo && !!task.branchName : false
1634
+ };
1635
+ }
1636
+ async function createPersonalProject(userId, name, description, repositoryUrl) {
1637
+ const user = await prisma.user.findUnique({
1638
+ where: { id: userId },
1639
+ include: { organizations: true }
1640
+ });
1641
+ if (!user) {
1642
+ throw new Error("User not found");
1643
+ }
1644
+ const repoInfo = GitHubProvider.parseRepositoryUrl(repositoryUrl);
1645
+ if (!repoInfo) {
1646
+ throw new Error("Invalid repository URL");
1647
+ }
1648
+ const github = getGitHubClient(user.organizations[0]?.organizationId || "");
1649
+ const branchInfo = await github.getBranchInfo({
1650
+ owner: repoInfo.owner,
1651
+ repo: repoInfo.repo,
1652
+ branch: "main"
1653
+ });
1654
+ if (!branchInfo) {
1655
+ throw new Error("Could not get repository branch info");
1656
+ }
1657
+ const project = await prisma.project.create({
1658
+ data: {
1659
+ name,
1660
+ description,
1661
+ type: ProjectType.PERSONAL,
1662
+ origin: ProjectOrigin.CREATED,
1663
+ ownerId: userId,
1664
+ organizationId: null,
1665
+ repositoryUrl,
1666
+ baseBranch: "main"
1667
+ }
1668
+ });
1669
+ return {
1670
+ success: true,
1671
+ projectId: project.id
1672
+ };
1673
+ }
1674
+
1675
+ export {
1676
+ createMCPServer
1677
+ };
1678
+ //# sourceMappingURL=chunk-4ILBJI7A.js.map