backlog-mcp-server 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.ja.md +31 -32
  2. package/README.md +36 -32
  3. package/build/auth/oauthRoutes.js +53 -1
  4. package/build/backlog/customFields.js +12 -9
  5. package/build/createBacklogMcpServer.d.ts +4 -6
  6. package/build/createBacklogMcpServer.js +4 -12
  7. package/build/handlers/builders/composeToolHandler.d.ts +1 -1
  8. package/build/handlers/builders/composeToolHandler.js +15 -9
  9. package/build/handlers/transformers/wrapWithFieldPicking.d.ts +14 -1
  10. package/build/handlers/transformers/wrapWithFieldPicking.js +30 -36
  11. package/build/httpMcpServer.js +8 -1
  12. package/build/index.js +20 -15
  13. package/build/lib.d.ts +1 -1
  14. package/build/loadDescriptionOverrides.js +2 -2
  15. package/build/reportUnknownOverrideKeys.js +0 -1
  16. package/build/tools/addDocument.d.ts +3 -3
  17. package/build/tools/addDocument.js +18 -3
  18. package/build/tools/addIssue.d.ts +3 -3
  19. package/build/tools/addIssue.js +32 -2
  20. package/build/tools/addIssueComment.d.ts +3 -3
  21. package/build/tools/addIssueComment.js +14 -2
  22. package/build/tools/addProject.d.ts +3 -3
  23. package/build/tools/addProject.js +22 -2
  24. package/build/tools/addPullRequest.d.ts +3 -3
  25. package/build/tools/addPullRequest.js +26 -2
  26. package/build/tools/addPullRequestComment.d.ts +3 -3
  27. package/build/tools/addPullRequestComment.js +12 -2
  28. package/build/tools/addRelatedIssue.d.ts +3 -3
  29. package/build/tools/addRelatedIssue.js +33 -2
  30. package/build/tools/addVersionMilestone.d.ts +3 -3
  31. package/build/tools/addVersionMilestone.js +12 -2
  32. package/build/tools/addWatching.d.ts +3 -3
  33. package/build/tools/addWatching.js +12 -2
  34. package/build/tools/addWiki.d.ts +3 -3
  35. package/build/tools/addWiki.js +16 -2
  36. package/build/tools/countIssues.d.ts +3 -3
  37. package/build/tools/countIssues.js +3 -2
  38. package/build/tools/deleteIssue.d.ts +3 -3
  39. package/build/tools/deleteIssue.js +32 -2
  40. package/build/tools/deleteProject.d.ts +3 -3
  41. package/build/tools/deleteProject.js +22 -2
  42. package/build/tools/deleteVersion.d.ts +3 -3
  43. package/build/tools/deleteVersion.js +12 -2
  44. package/build/tools/deleteWatching.d.ts +3 -3
  45. package/build/tools/deleteWatching.js +12 -2
  46. package/build/tools/getCategories.d.ts +3 -3
  47. package/build/tools/getCategories.js +8 -2
  48. package/build/tools/getCustomFields.d.ts +3 -3
  49. package/build/tools/getCustomFields.js +22 -2
  50. package/build/tools/getDocument.d.ts +3 -3
  51. package/build/tools/getDocument.js +17 -2
  52. package/build/tools/getDocumentTree.d.ts +3 -3
  53. package/build/tools/getDocumentTree.js +7 -2
  54. package/build/tools/getDocuments.d.ts +3 -3
  55. package/build/tools/getDocuments.js +17 -2
  56. package/build/tools/getGitRepositories.d.ts +3 -3
  57. package/build/tools/getGitRepositories.js +17 -2
  58. package/build/tools/getGitRepository.d.ts +3 -3
  59. package/build/tools/getGitRepository.js +17 -2
  60. package/build/tools/getIssue.d.ts +3 -3
  61. package/build/tools/getIssue.js +32 -2
  62. package/build/tools/getIssueComments.d.ts +3 -3
  63. package/build/tools/getIssueComments.js +14 -2
  64. package/build/tools/getIssueTypes.d.ts +3 -3
  65. package/build/tools/getIssueTypes.js +11 -2
  66. package/build/tools/getIssues.d.ts +17 -17
  67. package/build/tools/getIssues.js +32 -2
  68. package/build/tools/getMyself.d.ts +3 -3
  69. package/build/tools/getMyself.js +11 -2
  70. package/build/tools/getNotifications.d.ts +3 -3
  71. package/build/tools/getNotifications.js +15 -2
  72. package/build/tools/getNotificationsCount.d.ts +3 -3
  73. package/build/tools/getNotificationsCount.js +5 -2
  74. package/build/tools/getPriorities.d.ts +3 -3
  75. package/build/tools/getPriorities.js +3 -2
  76. package/build/tools/getProject.d.ts +3 -3
  77. package/build/tools/getProject.js +22 -2
  78. package/build/tools/getProjectList.d.ts +3 -3
  79. package/build/tools/getProjectList.js +22 -2
  80. package/build/tools/getProjectUsers.d.ts +3 -3
  81. package/build/tools/getProjectUsers.js +11 -2
  82. package/build/tools/getPullRequest.d.ts +3 -3
  83. package/build/tools/getPullRequest.js +26 -2
  84. package/build/tools/getPullRequestComments.d.ts +3 -3
  85. package/build/tools/getPullRequestComments.js +12 -2
  86. package/build/tools/getPullRequests.d.ts +3 -3
  87. package/build/tools/getPullRequests.js +26 -2
  88. package/build/tools/getPullRequestsCount.d.ts +3 -3
  89. package/build/tools/getPullRequestsCount.js +5 -2
  90. package/build/tools/getRelatedIssues.d.ts +3 -3
  91. package/build/tools/getRelatedIssues.js +33 -2
  92. package/build/tools/getResolutions.d.ts +3 -3
  93. package/build/tools/getResolutions.js +3 -2
  94. package/build/tools/getSpace.d.ts +3 -3
  95. package/build/tools/getSpace.js +13 -2
  96. package/build/tools/getSpaceActivities.d.ts +3 -3
  97. package/build/tools/getSpaceActivities.js +12 -2
  98. package/build/tools/getUserRecentUpdates.d.ts +3 -3
  99. package/build/tools/getUserRecentUpdates.js +12 -2
  100. package/build/tools/getUserStarsCount.d.ts +3 -3
  101. package/build/tools/getUserStarsCount.js +3 -2
  102. package/build/tools/getUsers.d.ts +3 -3
  103. package/build/tools/getUsers.js +11 -2
  104. package/build/tools/getVersionMilestoneList.d.ts +3 -3
  105. package/build/tools/getVersionMilestoneList.js +12 -2
  106. package/build/tools/getWatchingListCount.d.ts +3 -3
  107. package/build/tools/getWatchingListCount.js +5 -2
  108. package/build/tools/getWatchingListItems.d.ts +3 -3
  109. package/build/tools/getWatchingListItems.js +12 -2
  110. package/build/tools/getWiki.d.ts +3 -3
  111. package/build/tools/getWiki.js +16 -2
  112. package/build/tools/getWikiPages.d.ts +3 -3
  113. package/build/tools/getWikiPages.js +12 -2
  114. package/build/tools/getWikisCount.d.ts +3 -3
  115. package/build/tools/getWikisCount.js +3 -2
  116. package/build/tools/markNotificationAsRead.d.ts +7 -2
  117. package/build/tools/markNotificationAsRead.js +6 -1
  118. package/build/tools/markWatchingAsRead.d.ts +7 -2
  119. package/build/tools/markWatchingAsRead.js +6 -1
  120. package/build/tools/removeRelatedIssue.d.ts +3 -3
  121. package/build/tools/removeRelatedIssue.js +33 -2
  122. package/build/tools/resetUnreadNotificationCount.d.ts +3 -3
  123. package/build/tools/resetUnreadNotificationCount.js +5 -2
  124. package/build/tools/shared/customFieldFiltersSchema.d.ts +1 -1
  125. package/build/tools/updateIssue.d.ts +3 -3
  126. package/build/tools/updateIssue.js +32 -2
  127. package/build/tools/updateIssueComment.d.ts +3 -3
  128. package/build/tools/updateIssueComment.js +14 -2
  129. package/build/tools/updateProject.d.ts +3 -3
  130. package/build/tools/updateProject.js +22 -2
  131. package/build/tools/updatePullRequest.d.ts +3 -3
  132. package/build/tools/updatePullRequest.js +26 -2
  133. package/build/tools/updatePullRequestComment.d.ts +3 -3
  134. package/build/tools/updatePullRequestComment.js +12 -2
  135. package/build/tools/updateVersionMilestone.d.ts +3 -3
  136. package/build/tools/updateVersionMilestone.js +12 -2
  137. package/build/tools/updateWatching.d.ts +3 -3
  138. package/build/tools/updateWatching.js +12 -2
  139. package/build/tools/updateWiki.d.ts +3 -3
  140. package/build/tools/updateWiki.js +16 -2
  141. package/build/types/outputFields.d.ts +26 -0
  142. package/build/types/outputFields.js +3 -0
  143. package/build/types/tool.d.ts +22 -9
  144. package/build/types/zod/activityType.d.ts +38 -0
  145. package/build/types/zod/activityType.js +38 -0
  146. package/build/utils/fieldSelection.d.ts +22 -0
  147. package/build/utils/fieldSelection.js +40 -0
  148. package/build/utils/logger.js +41 -3
  149. package/build/utils/resolveIdOrKey.d.ts +2 -2
  150. package/build/utils/toolsetUtils.d.ts +1 -3
  151. package/build/utils/toolsetUtils.js +0 -12
  152. package/package.json +17 -25
  153. package/build/tools/dynamicTools/toolsets.d.ts +0 -15
  154. package/build/tools/dynamicTools/toolsets.js +0 -103
  155. package/build/types/zod/backlogOutputDefinition.d.ts +0 -3334
  156. package/build/types/zod/backlogOutputDefinition.js +0 -483
  157. package/build/utils/generateFieldsDescription.d.ts +0 -5
  158. package/build/utils/generateFieldsDescription.js +0 -50
  159. package/build/utils/toolRegistrar.d.ts +0 -5
  160. package/build/utils/toolRegistrar.js +0 -12
@@ -1,13 +1,13 @@
1
1
  import { z } from 'zod';
2
+ import type { Entity } from 'backlog-js';
2
3
  import { Backlog } from 'backlog-js';
3
4
  import { ToolDefinition } from '../types/tool.js';
4
5
  import { DescriptionHelper } from '../createDescriptionHelper.js';
5
- import { WikiSchema } from '../types/zod/backlogOutputDefinition.js';
6
- declare const updateWikiSchema: (t: DescriptionHelper["t"]) => {
6
+ declare const updateWikiSchema: (t: DescriptionHelper['t']) => {
7
7
  wikiId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
8
8
  name: z.ZodOptional<z.ZodString>;
9
9
  content: z.ZodOptional<z.ZodString>;
10
10
  mailNotify: z.ZodOptional<z.ZodBoolean>;
11
11
  };
12
- export declare const updateWikiTool: (backlog: Backlog, { t }: DescriptionHelper) => ToolDefinition<ReturnType<typeof updateWikiSchema>, (typeof WikiSchema)["shape"]>;
12
+ export declare const updateWikiTool: (backlog: Backlog, { t }: DescriptionHelper) => ToolDefinition<ReturnType<typeof updateWikiSchema>, Entity.Wiki.Wiki>;
13
13
  export {};
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { buildToolSchema } from '../types/tool.js';
3
- import { WikiSchema } from '../types/zod/backlogOutputDefinition.js';
3
+ import { outputFields } from '../types/outputFields.js';
4
4
  const updateWikiSchema = buildToolSchema((t) => ({
5
5
  wikiId: z
6
6
  .union([z.string(), z.number()])
@@ -23,7 +23,21 @@ export const updateWikiTool = (backlog, { t }) => {
23
23
  name: 'update_wiki',
24
24
  description: t('TOOL_UPDATE_WIKI_DESCRIPTION', 'Updates an existing wiki page'),
25
25
  schema: z.object(updateWikiSchema(t)),
26
- outputSchema: WikiSchema,
26
+ returnsList: false,
27
+ outputFields: outputFields()([
28
+ 'id',
29
+ 'projectId',
30
+ 'name',
31
+ 'content',
32
+ 'tags',
33
+ 'attachments',
34
+ 'sharedFiles',
35
+ 'stars',
36
+ 'createdUser',
37
+ 'created',
38
+ 'updatedUser',
39
+ 'updated',
40
+ ]),
27
41
  importantFields: ['id', 'name', 'content', 'updatedUser'],
28
42
  handler: async ({ wikiId, name, content, mailNotify }) => {
29
43
  const wikiIdNumber = typeof wikiId === 'string' ? parseInt(wikiId, 10) : wikiId;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The list of field names a tool's result carries.
3
+ *
4
+ * Only names are needed at runtime — `--optimize-response` publishes them as the
5
+ * enum of its `fields` parameter — and TypeScript types are erased, so the list
6
+ * has to be written out. What this adds is that writing it wrong does not
7
+ * compile: the names are checked against the `backlog-js` type in both
8
+ * directions, so a field that package gains, or one it never had, is a build
9
+ * error rather than a quiet difference.
10
+ *
11
+ * outputFields<Entity.Issue.Issue>()(['id', 'projectId', ...])
12
+ *
13
+ * Missing a name fails with the name in the message:
14
+ *
15
+ * Type '{ __outputFieldsMissing: "childIssueSummary"; }' is missing the
16
+ * following properties from type 'readonly (keyof Issue)[]'
17
+ *
18
+ * A name the type does not have fails with:
19
+ *
20
+ * Type '"nope"' is not assignable to type 'keyof Space'
21
+ */
22
+ type Missing<T, K extends readonly PropertyKey[]> = Exclude<keyof T, K[number]>;
23
+ export declare function outputFields<T>(): <K extends readonly (keyof T)[]>(keys: K) => [Missing<T, K>] extends [never] ? readonly (keyof T)[] : {
24
+ __outputFieldsMissing: Missing<T, K>;
25
+ };
26
+ export {};
@@ -0,0 +1,3 @@
1
+ export function outputFields() {
2
+ return (keys) => keys;
3
+ }
@@ -1,24 +1,37 @@
1
1
  import { z } from 'zod';
2
2
  import { DescriptionHelper } from '../createDescriptionHelper.js';
3
3
  import { CallToolResult } from '@modelcontextprotocol/server';
4
- export type ToolDefinition<Shape extends z.ZodRawShape, OutputShape extends z.ZodRawShape> = {
4
+ export type ToolDefinition<Shape extends z.ZodRawShape, Result> = {
5
5
  name: string;
6
6
  description: string;
7
7
  schema: z.ZodObject<Shape>;
8
- outputSchema: z.ZodObject<OutputShape>;
8
+ outputFields: readonly (keyof Result)[];
9
9
  handler: (input: z.infer<z.ZodObject<Shape>> & {
10
- fields?: string;
10
+ fields?: string[];
11
11
  organization?: string;
12
- }) => Promise<z.infer<z.ZodObject<OutputShape>> | z.infer<z.ZodObject<OutputShape>>[]>;
13
- importantFields?: (keyof z.infer<z.ZodObject<OutputShape>>)[];
12
+ }) => Promise<Result | Result[]>;
13
+ importantFields?: (keyof Result)[];
14
+ /**
15
+ * Whether the Backlog call behind this tool returns a list.
16
+ *
17
+ * `--optimize-response` only publishes its `fields` parameter on these. A list
18
+ * is the case where the response grows without bound — `get_issues` alone
19
+ * returns up to 100 issues — and where trimming it is worth the schema every
20
+ * client downloads. A tool that returns one record saves a few hundred bytes at
21
+ * best, against a cost paid on every session by everyone.
22
+ *
23
+ * Verifiable: it is true exactly for the tools whose `backlog-js` method is
24
+ * declared as `Promise<T[]>`.
25
+ *
26
+ * Required rather than optional so that a new tool has to answer the question.
27
+ * Left optional, forgetting it would silently mean "no field selection".
28
+ */
29
+ returnsList: boolean;
14
30
  };
15
- export declare const buildToolSchema: <T extends z.ZodRawShape>(fn: (t: DescriptionHelper["t"]) => T) => (t: DescriptionHelper["t"]) => T;
31
+ export declare const buildToolSchema: <T extends z.ZodRawShape>(fn: (t: DescriptionHelper['t']) => T) => (t: DescriptionHelper['t']) => T;
16
32
  export type DynamicToolDefinition<Shape extends z.ZodRawShape> = {
17
33
  name: string;
18
34
  description: string;
19
35
  schema: z.ZodObject<Shape>;
20
36
  handler: (input: z.infer<z.ZodObject<Shape>>) => Promise<CallToolResult>;
21
37
  };
22
- export interface ToolRegistrar {
23
- enableToolsetAndRefresh(toolset: string): Promise<string>;
24
- }
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The activity type ids the Backlog API accepts as a filter.
4
+ *
5
+ * All that is left of backlogOutputDefinition.ts. That file described API
6
+ * responses in parallel with the types backlog-js already ships; tools now take
7
+ * their field names from those types instead. This one is different: it is an
8
+ * input filter, and backlog-js has no enum for it.
9
+ */
10
+ export declare const ActivityTypeSchema: z.ZodEnum<{
11
+ Undefined: number;
12
+ IssueCreated: number;
13
+ IssueUpdated: number;
14
+ IssueCommented: number;
15
+ IssueDeleted: number;
16
+ WikiCreated: number;
17
+ WikiUpdated: number;
18
+ WikiDeleted: number;
19
+ FileAdded: number;
20
+ FileUpdated: number;
21
+ FileDeleted: number;
22
+ SvnCommitted: number;
23
+ GitPushed: number;
24
+ GitRepositoryCreated: number;
25
+ IssueMultiUpdated: number;
26
+ ProjectUserAdded: number;
27
+ ProjectUserRemoved: number;
28
+ NotifyAdded: number;
29
+ PullRequestAdded: number;
30
+ PullRequestUpdated: number;
31
+ PullRequestCommented: number;
32
+ PullRequestMerged: number;
33
+ MilestoneCreated: number;
34
+ MilestoneUpdated: number;
35
+ MilestoneDeleted: number;
36
+ ProjectGroupAdded: number;
37
+ ProjectGroupDeleted: number;
38
+ }>;
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The activity type ids the Backlog API accepts as a filter.
4
+ *
5
+ * All that is left of backlogOutputDefinition.ts. That file described API
6
+ * responses in parallel with the types backlog-js already ships; tools now take
7
+ * their field names from those types instead. This one is different: it is an
8
+ * input filter, and backlog-js has no enum for it.
9
+ */
10
+ export const ActivityTypeSchema = z.nativeEnum({
11
+ Undefined: -1,
12
+ IssueCreated: 1,
13
+ IssueUpdated: 2,
14
+ IssueCommented: 3,
15
+ IssueDeleted: 4,
16
+ WikiCreated: 5,
17
+ WikiUpdated: 6,
18
+ WikiDeleted: 7,
19
+ FileAdded: 8,
20
+ FileUpdated: 9,
21
+ FileDeleted: 10,
22
+ SvnCommitted: 11,
23
+ GitPushed: 12,
24
+ GitRepositoryCreated: 13,
25
+ IssueMultiUpdated: 14,
26
+ ProjectUserAdded: 15,
27
+ ProjectUserRemoved: 16,
28
+ NotifyAdded: 17,
29
+ PullRequestAdded: 18,
30
+ PullRequestUpdated: 19,
31
+ PullRequestCommented: 20,
32
+ PullRequestMerged: 21,
33
+ MilestoneCreated: 22,
34
+ MilestoneUpdated: 23,
35
+ MilestoneDeleted: 24,
36
+ ProjectGroupAdded: 25,
37
+ ProjectGroupDeleted: 26,
38
+ });
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Describes the `fields` parameter that `--optimize-response` adds to a tool.
4
+ *
5
+ * The selectable names come straight from the tool's `outputFields`, so they are
6
+ * published as a JSON Schema enum rather than described in prose. That is the
7
+ * whole point of the shape: the enum *is* the documentation, an unknown name is
8
+ * rejected by schema validation instead of being silently dropped, and the text
9
+ * shrinks to one sentence per tool.
10
+ *
11
+ * Selection is one level deep. It used to accept a GraphQL selection set, parsed
12
+ * with the `graphql` package, which cost ~1 MB for one `parse` call and accepted
13
+ * far more of the grammar than was ever honoured — aliases, fragments, arguments
14
+ * and directives all parsed and were then quietly ignored, and descending into an
15
+ * array field returned `{}` and lost the data.
16
+ */
17
+ export declare function fieldSelection(outputFields: readonly PropertyKey[], importantFields?: readonly PropertyKey[]): {
18
+ names: string[];
19
+ description: string;
20
+ } | undefined;
21
+ /** The `fields` parameter itself, or undefined when the tool has nothing to offer. */
22
+ export declare function fieldSelectionSchema(outputFields: readonly PropertyKey[], importantFields?: readonly PropertyKey[]): z.ZodOptional<z.ZodArray<z.ZodEnum<Record<string, string>>>> | undefined;
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Describes the `fields` parameter that `--optimize-response` adds to a tool.
4
+ *
5
+ * The selectable names come straight from the tool's `outputFields`, so they are
6
+ * published as a JSON Schema enum rather than described in prose. That is the
7
+ * whole point of the shape: the enum *is* the documentation, an unknown name is
8
+ * rejected by schema validation instead of being silently dropped, and the text
9
+ * shrinks to one sentence per tool.
10
+ *
11
+ * Selection is one level deep. It used to accept a GraphQL selection set, parsed
12
+ * with the `graphql` package, which cost ~1 MB for one `parse` call and accepted
13
+ * far more of the grammar than was ever honoured — aliases, fragments, arguments
14
+ * and directives all parsed and were then quietly ignored, and descending into an
15
+ * array field returned `{}` and lost the data.
16
+ */
17
+ export function fieldSelection(outputFields, importantFields = []) {
18
+ // `keyof T` admits numbers and symbols. Entity keys are all strings, but taking
19
+ // PropertyKey here keeps the caller from having to assert that.
20
+ const names = outputFields.map(String);
21
+ // `z.enum([])` is not constructible, and a tool with no known output fields has
22
+ // nothing to select from anyway.
23
+ if (names.length === 0)
24
+ return undefined;
25
+ const useful = importantFields.map(String).filter((f) => names.includes(f));
26
+ const description = useful.length > 0
27
+ ? `Return only these fields of the result, to keep the response small. Most callers want: ${useful.join(', ')}.`
28
+ : 'Return only these fields of the result, to keep the response small.';
29
+ return { names, description };
30
+ }
31
+ /** The `fields` parameter itself, or undefined when the tool has nothing to offer. */
32
+ export function fieldSelectionSchema(outputFields, importantFields = []) {
33
+ const selection = fieldSelection(outputFields, importantFields);
34
+ if (!selection)
35
+ return undefined;
36
+ return z
37
+ .array(z.enum(selection.names))
38
+ .optional()
39
+ .describe(selection.description);
40
+ }
@@ -2,10 +2,42 @@ import pino from 'pino';
2
2
  if (!process.env.NODE_ENV) {
3
3
  process.env.NODE_ENV = 'production';
4
4
  }
5
- const isProd = process.env.NODE_ENV === 'production';
5
+ // Normalised the same way LOG_LEVEL is below. A strict `=== 'production'` makes
6
+ // `Production`, or a stray space picked up from a shell or a container runtime,
7
+ // silently mean "not production" — and that flips the default level *and* the
8
+ // output format, with nothing logged to say so. A typo here is worth less
9
+ // tolerance than a typo in LOG_LEVEL, not more.
10
+ const isProd = process.env.NODE_ENV?.trim().toLowerCase() === 'production';
11
+ // pino's levels plus `silent`. Listed here so an unusable LOG_LEVEL is caught
12
+ // while the fallback is still available, rather than by pino throwing during
13
+ // construction — a server that refuses to start is a worse answer to a typo in
14
+ // an environment variable than one that logs at its default level and says so.
15
+ const LEVELS = [
16
+ 'fatal',
17
+ 'error',
18
+ 'warn',
19
+ 'info',
20
+ 'debug',
21
+ 'trace',
22
+ 'silent',
23
+ ];
24
+ const defaultLevel = isProd ? 'error' : 'debug';
25
+ // LOG_LEVEL decides how much is logged; NODE_ENV decides how it is formatted.
26
+ // The two are independent questions, and conflating them bites on the HTTP
27
+ // transport in particular: there the log stream is the only channel an operator
28
+ // has, so raising the level to diagnose something must not also switch the
29
+ // output to colorized, human-formatted lines that a log collector stores
30
+ // verbatim.
31
+ // An empty or blank LOG_LEVEL counts as not set: it is what an unset variable
32
+ // looks like once it has been through a shell or a container runtime.
33
+ const normalisedLevel = process.env.LOG_LEVEL?.trim().toLowerCase() || undefined;
34
+ const requestedLevel = normalisedLevel && LEVELS.includes(normalisedLevel)
35
+ ? normalisedLevel
36
+ : undefined;
37
+ const level = requestedLevel ?? defaultLevel;
6
38
  // Plain JSON on stderr. stdout carries the JSON-RPC stream on the stdio
7
39
  // transport, so a log line landing there would corrupt the protocol.
8
- const plainLogger = () => pino({ level: isProd ? 'error' : 'debug' }, pino.destination({ dest: 2, sync: false }));
40
+ const plainLogger = () => pino({ level }, pino.destination({ dest: 2, sync: false }));
9
41
  // `pino-pretty` is a development-only dependency, so an installed copy of this
10
42
  // package does not have it. pino resolves a transport target eagerly and throws
11
43
  // synchronously when it cannot, so asking is cheaper than probing the module
@@ -13,7 +45,7 @@ const plainLogger = () => pino({ level: isProd ? 'error' : 'debug' }, pino.desti
13
45
  const prettyLogger = () => {
14
46
  try {
15
47
  return pino({
16
- level: 'debug',
48
+ level,
17
49
  transport: {
18
50
  target: 'pino-pretty',
19
51
  options: {
@@ -31,3 +63,9 @@ const prettyLogger = () => {
31
63
  }
32
64
  };
33
65
  export const logger = isProd ? plainLogger() : prettyLogger();
66
+ if (normalisedLevel && !requestedLevel) {
67
+ // `logger.error`, not `warn`: the level in force here is the default one,
68
+ // which on a deployed server swallows anything lower — and this message
69
+ // exists precisely for the operator whose LOG_LEVEL did not take effect.
70
+ logger.error({ requested: process.env.LOG_LEVEL, effective: level, supported: LEVELS }, 'Unrecognised LOG_LEVEL; falling back to the default level');
71
+ }
@@ -10,9 +10,9 @@ type ResolveResult = {
10
10
  export declare const resolveIdOrKey: <E extends EntityName>(entity: E, values: {
11
11
  id?: number;
12
12
  key?: string;
13
- }, t: DescriptionHelper["t"]) => ResolveResult;
13
+ }, t: DescriptionHelper['t']) => ResolveResult;
14
14
  export declare const resolveIdOrName: <E extends EntityName>(entity: E, values: {
15
15
  id?: number;
16
16
  name?: string;
17
- }, t: DescriptionHelper["t"]) => ResolveResult;
17
+ }, t: DescriptionHelper['t']) => ResolveResult;
18
18
  export {};
@@ -1,6 +1,4 @@
1
1
  import { Backlog } from 'backlog-js';
2
- import { ToolsetGroup, Toolset } from '../types/toolsets.js';
2
+ import { ToolsetGroup } from '../types/toolsets.js';
3
3
  import { DescriptionHelper } from '../createDescriptionHelper.js';
4
- export declare function getToolset(group: ToolsetGroup, name: string): Toolset | undefined;
5
- export declare function enableToolset(group: ToolsetGroup, name: string): string;
6
4
  export declare const buildToolsetGroup: (backlog: Backlog, helper: DescriptionHelper, enabledToolsets: string[]) => ToolsetGroup;
@@ -1,16 +1,4 @@
1
1
  import { allTools } from '../tools/tools.js';
2
- export function getToolset(group, name) {
3
- return group.toolsets.find((t) => t.name === name);
4
- }
5
- export function enableToolset(group, name) {
6
- const ts = getToolset(group, name);
7
- if (!ts)
8
- return `Toolset ${name} not found`;
9
- if (ts.enabled)
10
- return `Toolset ${name} is already enabled`;
11
- ts.enabled = true;
12
- return `Toolset ${name} enabled`;
13
- }
14
2
  export const buildToolsetGroup = (backlog, helper, enabledToolsets) => {
15
3
  const toolsetGroup = allTools(backlog, helper);
16
4
  const knownNames = toolsetGroup.toolsets.map((ts) => ts.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backlog-mcp-server",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "backlog-mcp-server": "./build/index.js"
@@ -33,35 +33,29 @@
33
33
  "build"
34
34
  ],
35
35
  "dependencies": {
36
- "@hono/node-server": "^2.0.10",
36
+ "@hono/node-server": "^2.1.1",
37
37
  "@modelcontextprotocol/hono": "^2.0.0",
38
38
  "@modelcontextprotocol/server": "^2.0.0",
39
39
  "backlog-js": "^0.19.1",
40
40
  "env-var": "^7.5.0",
41
- "graphql": "^17.0.2",
42
- "hono": "^4.12.34",
43
- "js-yaml": "^5.2.3",
41
+ "hono": "^4.13.5",
42
+ "js-yaml": "^5.4.1",
44
43
  "pino": "^10.3.1",
45
- "yargs": "^18.0.0",
44
+ "yargs": "^18.1.0",
46
45
  "zod": "^4.4.3"
47
46
  },
48
47
  "devDependencies": {
49
- "@eslint/js": "^10.0.1",
50
48
  "@types/js-yaml": "^4.0.9",
51
- "@types/node": "^26.2.0",
49
+ "@types/node": "^26.4.0",
52
50
  "@types/yargs": "^17.0.35",
53
- "@typescript-eslint/eslint-plugin": "^8.60.1",
54
- "@typescript-eslint/parser": "^8.60.1",
55
- "@typescript-eslint/utils": "^8.60.1",
56
- "@vitest/coverage-v8": "^4.1.8",
57
- "eslint": "^10.4.1",
58
- "eslint-config-prettier": "^10.1.8",
59
- "eslint-plugin-prettier": "^5.5.6",
51
+ "@vitest/coverage-v8": "^4.1.11",
52
+ "oxfmt": "^0.65.0",
53
+ "oxlint": "^1.80.0",
54
+ "oxlint-tsgolint": "^7.0.2001",
60
55
  "pino-pretty": "^13.1.3",
61
- "prettier": "^3.8.3",
62
- "tsx": "^4.22.4",
63
- "typescript": "^6.0.3",
64
- "vitest": "^4.1.8"
56
+ "tsx": "^4.23.12",
57
+ "typescript": "^7.0.2",
58
+ "vitest": "^4.1.11"
65
59
  },
66
60
  "scripts": {
67
61
  "preinstall": "npx only-allow pnpm",
@@ -70,11 +64,9 @@
70
64
  "test": "vitest run",
71
65
  "test:watch": "vitest",
72
66
  "test:coverage": "vitest run --coverage",
73
- "lint": "eslint . --ext .ts",
74
- "lint:fix": "eslint . --ext .ts --fix",
75
- "format": "prettier --check \"**/*.{ts,tsx}\"",
76
- "format:fix": "prettier --write \"**/*.{ts,tsx}\"",
77
- "typecheck": "tsc --noEmit",
78
- "typecheck:all": "tsc --noEmit --project tsconfig.test.json"
67
+ "lint": "oxlint --type-aware --type-check",
68
+ "lint:fix": "oxlint --type-aware --type-check --fix",
69
+ "format": "oxfmt --check src vitest.config.ts",
70
+ "format:fix": "oxfmt src vitest.config.ts"
79
71
  }
80
72
  }
@@ -1,15 +0,0 @@
1
- import { z } from 'zod';
2
- import { DynamicToolDefinition, ToolRegistrar } from '../../types/tool.js';
3
- import { DynamicToolsetGroup, ToolsetGroup } from '../../types/toolsets.js';
4
- import { DescriptionHelper } from '../../createDescriptionHelper.js';
5
- export declare const dynamicTools: (toolRegistrar: ToolRegistrar, helper: DescriptionHelper, toolsetGroup: ToolsetGroup) => DynamicToolsetGroup;
6
- declare const enableToolsetSchema: (t: DescriptionHelper["t"]) => {
7
- toolset: z.ZodString;
8
- };
9
- export declare const enableToolsetTool: (toolRegistrar: ToolRegistrar, { t }: DescriptionHelper) => DynamicToolDefinition<ReturnType<typeof enableToolsetSchema>>;
10
- export declare const listAvailableToolsets: ({ t }: DescriptionHelper, toolsetGroup: ToolsetGroup) => DynamicToolDefinition<Record<string, never>>;
11
- declare const getToolsetToolsSchema: (t: DescriptionHelper["t"]) => {
12
- toolset: z.ZodString;
13
- };
14
- export declare const getToolsetTools: ({ t }: DescriptionHelper, toolsetGroup: ToolsetGroup) => DynamicToolDefinition<ReturnType<typeof getToolsetToolsSchema>>;
15
- export {};
@@ -1,103 +0,0 @@
1
- import { z } from 'zod';
2
- import { buildToolSchema, } from '../../types/tool.js';
3
- export const dynamicTools = function (toolRegistrar, helper, toolsetGroup) {
4
- return {
5
- toolsets: [
6
- {
7
- name: 'dynamic_tools',
8
- description: 'Tools for managing Backlog space settings and general information.',
9
- enabled: true,
10
- tools: [
11
- enableToolsetTool(toolRegistrar, helper),
12
- listAvailableToolsets(helper, toolsetGroup),
13
- getToolsetTools(helper, toolsetGroup),
14
- ],
15
- },
16
- ],
17
- };
18
- };
19
- const enableToolsetSchema = buildToolSchema((t) => ({
20
- toolset: z
21
- .string()
22
- .describe(t('TOOL_ENABLE_TOOLSET_TOOLSET', 'Enable a toolset')),
23
- }));
24
- export const enableToolsetTool = (toolRegistrar, { t }) => {
25
- return {
26
- name: 'enable_toolset',
27
- description: t('TOOL_ENABLE_TOOLSET_DESCRIPTION', 'Enable one of the Backlog MCP server toolsets. Use get_toolset_tools and list_available_toolsets first to inspect what this will enable.'),
28
- schema: z.object(enableToolsetSchema(t)),
29
- handler: async ({ toolset }) => {
30
- const msg = await toolRegistrar.enableToolsetAndRefresh(toolset);
31
- return {
32
- content: [
33
- {
34
- type: 'text',
35
- text: msg,
36
- },
37
- ],
38
- };
39
- },
40
- };
41
- };
42
- export const listAvailableToolsets = ({ t }, toolsetGroup) => {
43
- return {
44
- name: 'list_available_toolsets',
45
- description: t('TOOL_LIST_AVAILABLE_TOOLSETS_DESCRIPTION', 'List all available toolsets.'),
46
- schema: z.object({}),
47
- handler: async () => {
48
- const result = toolsetGroup.toolsets.map((ts) => ({
49
- name: ts.name,
50
- description: ts.description,
51
- currentlyEnabled: ts.enabled,
52
- canEnable: true,
53
- }));
54
- return {
55
- content: [
56
- {
57
- type: 'text',
58
- text: JSON.stringify(result, null, 2),
59
- },
60
- ],
61
- };
62
- },
63
- };
64
- };
65
- const getToolsetToolsSchema = buildToolSchema((t) => ({
66
- toolset: z
67
- .string()
68
- .describe(t('TOOL_GET_TOOLSET_TOOLS_TOOLSET', 'Toolset name to inspect')),
69
- }));
70
- export const getToolsetTools = ({ t }, toolsetGroup) => {
71
- return {
72
- name: 'get_toolset_tools',
73
- description: t('TOOL_GET_TOOLSET_TOOLS_DESCRIPTION', 'List all tools in a specific toolset.'),
74
- schema: z.object(getToolsetToolsSchema(t)),
75
- handler: async ({ toolset }) => {
76
- const found = toolsetGroup.toolsets.find((ts) => ts.name === toolset);
77
- if (!found) {
78
- return {
79
- content: [
80
- {
81
- type: 'text',
82
- text: `Toolset '${toolset}' not found.`,
83
- },
84
- ],
85
- };
86
- }
87
- const tools = found.tools.map((tool) => ({
88
- name: tool.name,
89
- description: tool.description,
90
- toolset: found.name,
91
- canEnable: true,
92
- }));
93
- return {
94
- content: [
95
- {
96
- type: 'text',
97
- text: JSON.stringify(tools, null, 2),
98
- },
99
- ],
100
- };
101
- },
102
- };
103
- };