backlog-mcp-server 0.15.0 → 0.15.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.
- package/README.ja.md +4 -2
- package/README.md +4 -2
- package/build/auth/backlogAuthContext.d.ts +2 -0
- package/build/auth/backlogOAuthClient.d.ts +10 -0
- package/build/auth/backlogOAuthConfig.d.ts +9 -0
- package/build/auth/bearerAuthMiddleware.d.ts +4 -0
- package/build/auth/oauthRoutes.d.ts +4 -0
- package/build/auth/tokenStore.d.ts +62 -0
- package/build/backlog/backlogErrorHandler.d.ts +2 -0
- package/build/backlog/customFields.d.ts +29 -0
- package/build/backlog/parseBacklogAPIError.d.ts +8 -0
- package/build/createBacklogMcpServer.d.ts +29 -0
- package/build/createBacklogMcpServer.js +6 -1
- package/build/createTranslationHelper.d.ts +16 -0
- package/build/createTranslationHelper.js +7 -11
- package/build/handlers/builders/composeToolHandler.d.ts +31 -0
- package/build/handlers/builders/composeToolHandler.js +21 -9
- package/build/handlers/transformers/wrapWithErrorHandling.d.ts +2 -0
- package/build/handlers/transformers/wrapWithFieldPicking.d.ts +4 -0
- package/build/handlers/transformers/wrapWithOrganizationContext.d.ts +3 -0
- package/build/handlers/transformers/wrapWithTokenLimit.d.ts +2 -0
- package/build/handlers/transformers/wrapWithToolResult.d.ts +6 -0
- package/build/httpMcpServer.d.ts +22 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +8 -2
- package/build/lib.d.ts +25 -0
- package/build/lib.js +20 -0
- package/build/loadTranslationOverrides.d.ts +17 -0
- package/build/loadTranslationOverrides.js +24 -0
- package/build/registerTools.d.ts +5 -0
- package/build/registerTools.js +7 -6
- package/build/tools/addDocument.d.ts +15 -0
- package/build/tools/addIssue.d.ts +30 -0
- package/build/tools/addIssueComment.d.ts +14 -0
- package/build/tools/addProject.d.ts +18 -0
- package/build/tools/addPullRequest.d.ts +20 -0
- package/build/tools/addPullRequestComment.d.ts +16 -0
- package/build/tools/addRelatedIssue.d.ts +12 -0
- package/build/tools/addVersionMilestone.d.ts +15 -0
- package/build/tools/addWatching.d.ts +11 -0
- package/build/tools/addWiki.d.ts +13 -0
- package/build/tools/countIssues.d.ts +48 -0
- package/build/tools/deleteIssue.d.ts +11 -0
- package/build/tools/deleteProject.d.ts +11 -0
- package/build/tools/deleteVersion.d.ts +12 -0
- package/build/tools/deleteWatching.d.ts +10 -0
- package/build/tools/dynamicTools/organizations.d.ts +6 -0
- package/build/tools/dynamicTools/toolsets.d.ts +15 -0
- package/build/tools/getCategories.d.ts +11 -0
- package/build/tools/getCustomFields.d.ts +12 -0
- package/build/tools/getDocument.d.ts +10 -0
- package/build/tools/getDocumentTree.d.ts +10 -0
- package/build/tools/getDocuments.d.ts +11 -0
- package/build/tools/getGitRepositories.d.ts +11 -0
- package/build/tools/getGitRepository.d.ts +13 -0
- package/build/tools/getIssue.d.ts +11 -0
- package/build/tools/getIssueComments.d.ts +18 -0
- package/build/tools/getIssueTypes.d.ts +11 -0
- package/build/tools/getIssues.d.ts +75 -0
- package/build/tools/getMyself.d.ts +7 -0
- package/build/tools/getNotifications.d.ts +16 -0
- package/build/tools/getNotificationsCount.d.ts +11 -0
- package/build/tools/getPriorities.d.ts +7 -0
- package/build/tools/getProject.d.ts +11 -0
- package/build/tools/getProjectList.d.ts +11 -0
- package/build/tools/getProjectUsers.d.ts +11 -0
- package/build/tools/getPullRequest.d.ts +14 -0
- package/build/tools/getPullRequestComments.d.ts +21 -0
- package/build/tools/getPullRequests.d.ts +19 -0
- package/build/tools/getPullRequestsCount.d.ts +17 -0
- package/build/tools/getRelatedIssues.d.ts +11 -0
- package/build/tools/getResolutions.d.ts +7 -0
- package/build/tools/getSpace.d.ts +7 -0
- package/build/tools/getSpaceActivities.d.ts +45 -0
- package/build/tools/getUserRecentUpdates.d.ts +46 -0
- package/build/tools/getUserStarsCount.d.ts +12 -0
- package/build/tools/getUsers.d.ts +7 -0
- package/build/tools/getVersionMilestoneList.d.ts +11 -0
- package/build/tools/getWatchingListCount.d.ts +10 -0
- package/build/tools/getWatchingListItems.d.ts +10 -0
- package/build/tools/getWiki.d.ts +10 -0
- package/build/tools/getWikiPages.d.ts +12 -0
- package/build/tools/getWikisCount.d.ts +11 -0
- package/build/tools/markNotificationAsRead.d.ts +13 -0
- package/build/tools/markWatchingAsRead.d.ts +13 -0
- package/build/tools/removeRelatedIssue.d.ts +12 -0
- package/build/tools/resetUnreadNotificationCount.d.ts +7 -0
- package/build/tools/shared/customFieldFiltersSchema.d.ts +21 -0
- package/build/tools/tools.d.ts +4 -0
- package/build/tools/updateIssue.d.ts +34 -0
- package/build/tools/updateIssueComment.d.ts +13 -0
- package/build/tools/updateProject.d.ts +21 -0
- package/build/tools/updatePullRequest.d.ts +20 -0
- package/build/tools/updatePullRequestComment.d.ts +16 -0
- package/build/tools/updateVersionMilestone.d.ts +17 -0
- package/build/tools/updateWatching.d.ts +11 -0
- package/build/tools/updateWiki.d.ts +13 -0
- package/build/types/mcp.d.ts +10 -0
- package/build/types/result.d.ts +10 -0
- package/build/types/tool.d.ts +24 -0
- package/build/types/toolsets.d.ts +16 -0
- package/build/types/zod/backlogOutputDefinition.d.ts +3334 -0
- package/build/types/zod/backlogOutputDefinition.js +0 -16
- package/build/utils/backlogClientRegistry.d.ts +28 -0
- package/build/utils/backlogClientRegistry.js +4 -0
- package/build/utils/backlogOrganizationContext.d.ts +2 -0
- package/build/utils/generateFieldsDescription.d.ts +5 -0
- package/build/utils/logger.d.ts +2 -0
- package/build/utils/logger.js +27 -14
- package/build/utils/resolveIdOrKey.d.ts +18 -0
- package/build/utils/runToolSafely.d.ts +6 -0
- package/build/utils/tokenCounter.d.ts +1 -0
- package/build/utils/toolRegistrar.d.ts +5 -0
- package/build/utils/toolsetUtils.d.ts +6 -0
- package/build/utils/toolsetUtils.js +0 -20
- package/build/utils/wrapServerWithToolRegistry.d.ts +6 -0
- package/package.json +11 -2
package/README.ja.md
CHANGED
|
@@ -596,7 +596,9 @@ BACKLOG_API_KEY=your-api-key
|
|
|
596
596
|
|
|
597
597
|
### ツールの使い方
|
|
598
598
|
|
|
599
|
-
|
|
599
|
+
マルチ組織用の環境変数が設定されている場合、通常のツールはすべて任意の`organization`入力フィールドを受け付けます。指定した場合、そのBacklog組織に対してツールが実行されます。
|
|
600
|
+
|
|
601
|
+
単一組織モードでは、ルーティング先が1つしかないためこのフィールドは公開されません。これにより`tools/list`のレスポンスから約8KBのスキーマが削減されます。
|
|
600
602
|
|
|
601
603
|
例:
|
|
602
604
|
|
|
@@ -614,7 +616,7 @@ BACKLOG_API_KEY=your-api-key
|
|
|
614
616
|
|
|
615
617
|
### 組織一覧の確認
|
|
616
618
|
|
|
617
|
-
|
|
619
|
+
マルチ組織モードでは、サーバーは `list_organizations` ツールを提供しており、設定済みの組織名、ドメイン、デフォルト組織かどうかを返します。単一組織モードでは登録されません。
|
|
618
620
|
|
|
619
621
|
レスポンス例:
|
|
620
622
|
|
package/README.md
CHANGED
|
@@ -715,7 +715,9 @@ BACKLOG_API_KEY=your-api-key
|
|
|
715
715
|
|
|
716
716
|
### Tool Usage
|
|
717
717
|
|
|
718
|
-
|
|
718
|
+
When multi-organization env vars are configured, all normal tools accept an optional `organization` input field. When provided, the tool call is routed to that Backlog organization.
|
|
719
|
+
|
|
720
|
+
In single-organization mode the field is not published, since there would be only one organization to route to. Omitting it keeps roughly 8 KB of tool schema out of every `tools/list` response.
|
|
719
721
|
|
|
720
722
|
Examples:
|
|
721
723
|
|
|
@@ -733,7 +735,7 @@ If `organization` is omitted:
|
|
|
733
735
|
|
|
734
736
|
### Organization Discovery
|
|
735
737
|
|
|
736
|
-
|
|
738
|
+
In multi-organization mode the server provides a `list_organizations` tool that returns the configured organization names, their domains, and which one is the default. It is not registered in single-organization mode.
|
|
737
739
|
|
|
738
740
|
Example response:
|
|
739
741
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BacklogOAuthConfig } from './backlogOAuthConfig.js';
|
|
2
|
+
import type { BacklogTokenData } from './tokenStore.js';
|
|
3
|
+
export declare function buildBacklogAuthorizationUrl(config: BacklogOAuthConfig, redirectUri: string, state: string): string;
|
|
4
|
+
export declare function exchangeBacklogCode(config: BacklogOAuthConfig, code: string, redirectUri: string): Promise<BacklogTokenData>;
|
|
5
|
+
export declare function refreshBacklogToken(config: BacklogOAuthConfig, refreshToken: string): Promise<BacklogTokenData>;
|
|
6
|
+
export declare function verifyBacklogToken(domain: string, accessToken: string): Promise<{
|
|
7
|
+
id: number;
|
|
8
|
+
userId: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type BacklogOAuthConfig = {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
backlogDomain: string;
|
|
5
|
+
serverBaseUrl: string;
|
|
6
|
+
};
|
|
7
|
+
type Environment = Record<string, string | undefined>;
|
|
8
|
+
export declare function getBacklogOAuthConfig(env?: Environment): BacklogOAuthConfig | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { BacklogOAuthConfig } from './backlogOAuthConfig.js';
|
|
3
|
+
import type { TokenStore } from './tokenStore.js';
|
|
4
|
+
export declare function createBearerAuthMiddleware(store: TokenStore, config: BacklogOAuthConfig, mcpPath: string): MiddlewareHandler;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AuthInfo } from '@modelcontextprotocol/server';
|
|
2
|
+
export type BacklogTokenData = {
|
|
3
|
+
access_token: string;
|
|
4
|
+
token_type: string;
|
|
5
|
+
expires_in: number;
|
|
6
|
+
refresh_token: string;
|
|
7
|
+
};
|
|
8
|
+
export type OAuthClientInfo = {
|
|
9
|
+
client_id: string;
|
|
10
|
+
client_secret?: string;
|
|
11
|
+
client_id_issued_at: number;
|
|
12
|
+
client_secret_expires_at: number;
|
|
13
|
+
redirect_uris: string[];
|
|
14
|
+
client_name?: string;
|
|
15
|
+
token_endpoint_auth_method?: string;
|
|
16
|
+
grant_types?: string[];
|
|
17
|
+
response_types?: string[];
|
|
18
|
+
};
|
|
19
|
+
type PendingAuthorization = {
|
|
20
|
+
mcpClientId: string;
|
|
21
|
+
codeChallenge: string;
|
|
22
|
+
redirectUri: string;
|
|
23
|
+
resource?: string;
|
|
24
|
+
scopes: string[];
|
|
25
|
+
state?: string;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
};
|
|
28
|
+
type AuthCodeEntry = {
|
|
29
|
+
mcpClientId: string;
|
|
30
|
+
backlogTokens: BacklogTokenData;
|
|
31
|
+
codeChallenge: string;
|
|
32
|
+
redirectUri: string;
|
|
33
|
+
resource?: string;
|
|
34
|
+
expiresAt: number;
|
|
35
|
+
};
|
|
36
|
+
export type McpTokenEntry = {
|
|
37
|
+
backlogAccessToken: string;
|
|
38
|
+
clientId: string;
|
|
39
|
+
expiresAt: number;
|
|
40
|
+
};
|
|
41
|
+
type McpRefreshEntry = {
|
|
42
|
+
backlogRefreshToken: string;
|
|
43
|
+
clientId: string;
|
|
44
|
+
expiresAt: number;
|
|
45
|
+
};
|
|
46
|
+
export type TokenStore = ReturnType<typeof createTokenStore>;
|
|
47
|
+
export declare function createTokenStore(): {
|
|
48
|
+
storePendingAuth(backlogState: string, pending: PendingAuthorization): void;
|
|
49
|
+
consumePendingAuth(backlogState: string): PendingAuthorization | undefined;
|
|
50
|
+
storeAuthCode(code: string, entry: AuthCodeEntry): void;
|
|
51
|
+
consumeAuthCode(code: string): AuthCodeEntry | undefined;
|
|
52
|
+
getClient(clientId: string): OAuthClientInfo | undefined;
|
|
53
|
+
registerClient(client: OAuthClientInfo): boolean;
|
|
54
|
+
getCachedVerification(token: string): AuthInfo | undefined;
|
|
55
|
+
cacheVerification(token: string, authInfo: AuthInfo, ttlMs: number): void;
|
|
56
|
+
storeMcpToken(mcpToken: string, entry: McpTokenEntry): void;
|
|
57
|
+
getMcpToken(mcpToken: string): McpTokenEntry | undefined;
|
|
58
|
+
storeMcpRefreshToken(mcpRefreshToken: string, entry: McpRefreshEntry): void;
|
|
59
|
+
consumeMcpRefreshToken(mcpRefreshToken: string): McpRefreshEntry | undefined;
|
|
60
|
+
cleanup(): void;
|
|
61
|
+
};
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type CustomFieldInput = {
|
|
2
|
+
id: number;
|
|
3
|
+
value?: string | number | string[] | number[];
|
|
4
|
+
otherValue?: string;
|
|
5
|
+
};
|
|
6
|
+
export type CustomFieldFilterInput = {
|
|
7
|
+
id: number;
|
|
8
|
+
type: 'text';
|
|
9
|
+
value: string;
|
|
10
|
+
} | {
|
|
11
|
+
id: number;
|
|
12
|
+
type: 'numeric';
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
} | {
|
|
16
|
+
id: number;
|
|
17
|
+
type: 'date';
|
|
18
|
+
min?: string;
|
|
19
|
+
max?: string;
|
|
20
|
+
} | {
|
|
21
|
+
id: number;
|
|
22
|
+
type: 'list';
|
|
23
|
+
value: number | number[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Converts Backlog-style customFields array into proper payload format
|
|
27
|
+
*/
|
|
28
|
+
export declare function customFieldsToPayload(customFields: CustomFieldInput[] | undefined): Record<string, string | number | string[] | number[] | undefined>;
|
|
29
|
+
export declare function customFieldFiltersToPayload(customFields: CustomFieldFilterInput[] | undefined): Record<string, string | number | number[] | undefined>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ParsedBacklogAPIError = {
|
|
2
|
+
type: 'BacklogAuthError' | 'BacklogApiError' | 'UnexpectedError' | 'UnknownError';
|
|
3
|
+
message: string;
|
|
4
|
+
status?: number;
|
|
5
|
+
code?: number;
|
|
6
|
+
url?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseBacklogAPIError(err: unknown): ParsedBacklogAPIError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Backlog } from 'backlog-js';
|
|
2
|
+
import type { TranslationHelper } from './createTranslationHelper.js';
|
|
3
|
+
import type { MCPOptions } from './types/mcp.js';
|
|
4
|
+
import type { ToolsetGroup } from './types/toolsets.js';
|
|
5
|
+
import type { BacklogClientRegistry } from './utils/backlogClientRegistry.js';
|
|
6
|
+
import { type BacklogMCPServer } from './utils/wrapServerWithToolRegistry.js';
|
|
7
|
+
export type CreateBacklogMcpServerConfig = {
|
|
8
|
+
version: string;
|
|
9
|
+
useFields: boolean;
|
|
10
|
+
backlog: Backlog;
|
|
11
|
+
clientRegistry: BacklogClientRegistry;
|
|
12
|
+
transHelper: TranslationHelper;
|
|
13
|
+
enabledToolsets: string[];
|
|
14
|
+
mcpOption: MCPOptions;
|
|
15
|
+
dynamicToolsets: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* A toolset group to register from, instead of building one from
|
|
18
|
+
* `enabledToolsets`. Callers that produce many servers from one factory pass
|
|
19
|
+
* a single shared group: `enable_toolset` mutates it, and under the stateless
|
|
20
|
+
* HTTP model — one server per request — a group built per server would throw
|
|
21
|
+
* that mutation away the moment the request ends.
|
|
22
|
+
*/
|
|
23
|
+
toolsetGroup?: ToolsetGroup;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Builds a fresh MCP server instance with all Backlog tools registered.
|
|
27
|
+
* Used once per stdio connection; one instance per HTTP request for Streamable HTTP.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createBacklogMcpServer({ version, useFields, backlog, clientRegistry, transHelper, enabledToolsets, mcpOption, dynamicToolsets, toolsetGroup: sharedToolsetGroup, }: CreateBacklogMcpServerConfig): BacklogMCPServer;
|
|
@@ -26,7 +26,12 @@ export function createBacklogMcpServer({ version, useFields, backlog, clientRegi
|
|
|
26
26
|
const toolsetGroup = sharedToolsetGroup ??
|
|
27
27
|
buildToolsetGroup(backlog, transHelper, enabledToolsets);
|
|
28
28
|
registerTools(server, toolsetGroup, mcpOption);
|
|
29
|
-
|
|
29
|
+
// `list_organizations` only has something to report when more than one space
|
|
30
|
+
// is configured; the `organization` parameter its description points at is
|
|
31
|
+
// published under the same condition.
|
|
32
|
+
if (mcpOption.useOrganization) {
|
|
33
|
+
registerDynamicTools(server, organizationTools(clientRegistry, transHelper), mcpOption.prefix);
|
|
34
|
+
}
|
|
30
35
|
if (dynamicToolsets) {
|
|
31
36
|
const registrar = createToolRegistrar(server, toolsetGroup, mcpOption);
|
|
32
37
|
const dynamicToolsetGroup = dynamicTools(registrar, transHelper, toolsetGroup);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the schema strings every tool definition passes through `t()`: tool
|
|
3
|
+
* descriptions and parameter descriptions. These are read by the model when it
|
|
4
|
+
* picks a tool and fills in arguments; they never reach the end user.
|
|
5
|
+
*
|
|
6
|
+
* This module intentionally imports nothing. Every tool in `src/tools/` depends on
|
|
7
|
+
* it, so anything imported here is reachable from the tool layer — and the tool
|
|
8
|
+
* layer is meant to run on non-Node runtimes too. Discovering and reading the
|
|
9
|
+
* override file needs a filesystem and a home directory, so that part lives in
|
|
10
|
+
* `loadTranslationOverrides` and the CLI passes the result in.
|
|
11
|
+
*/
|
|
12
|
+
export interface TranslationHelper {
|
|
13
|
+
t: (key: string, fallback: string) => string;
|
|
14
|
+
dump: () => Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createTranslationHelper(overrides?: Record<string, string>): TranslationHelper;
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import os from 'os';
|
|
3
|
-
export function createTranslationHelper(options) {
|
|
1
|
+
export function createTranslationHelper(overrides = {}) {
|
|
4
2
|
const usedKeys = {};
|
|
5
|
-
const configName = options?.configName ?? 'backlog-mcp-server';
|
|
6
|
-
// Load config file
|
|
7
|
-
const explorer = cosmiconfigSync(configName);
|
|
8
|
-
const searchPath = options?.searchDir ?? os.homedir();
|
|
9
|
-
const configResult = explorer.search(searchPath);
|
|
10
|
-
const config = configResult?.config || {};
|
|
11
3
|
function toEnvKey(key) {
|
|
12
4
|
return `BACKLOG_MCP_${key}`;
|
|
13
5
|
}
|
|
@@ -16,8 +8,12 @@ export function createTranslationHelper(options) {
|
|
|
16
8
|
if (usedKeys[upperKey]) {
|
|
17
9
|
return usedKeys[upperKey];
|
|
18
10
|
}
|
|
19
|
-
//
|
|
20
|
-
|
|
11
|
+
// Runtimes without a Node compatibility layer have no `process`, and a
|
|
12
|
+
// partial shim can have `process` without `env`.
|
|
13
|
+
const env = typeof process === 'undefined' ? undefined : process.env;
|
|
14
|
+
const fromEnv = env?.[toEnvKey(upperKey)];
|
|
15
|
+
// Priority:ENV → overrides → fallback
|
|
16
|
+
const value = fromEnv || overrides[upperKey] || fallback;
|
|
21
17
|
usedKeys[upperKey] = value;
|
|
22
18
|
return value;
|
|
23
19
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ErrorLike } from '../../types/result.js';
|
|
3
|
+
import { ToolDefinition } from '../../types/tool.js';
|
|
4
|
+
export interface ComposeOptions {
|
|
5
|
+
useFields: boolean;
|
|
6
|
+
errorHandler?: (err: unknown) => ErrorLike;
|
|
7
|
+
maxTokens: number;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to advertise `organization`. Defaults to false: with a single
|
|
10
|
+
* Backlog space the parameter has exactly one legal value, and repeating it
|
|
11
|
+
* across every tool costs the client ~8 KB of schema for nothing.
|
|
12
|
+
*/
|
|
13
|
+
useOrganization?: boolean;
|
|
14
|
+
}
|
|
15
|
+
type ComposedInput = {
|
|
16
|
+
fields?: string;
|
|
17
|
+
organization?: string;
|
|
18
|
+
} & Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Builds the schema and handler a tool is registered with.
|
|
21
|
+
*
|
|
22
|
+
* The returned schema is a fresh object: the tool definition is never mutated.
|
|
23
|
+
* That matters under the stateless HTTP model, where one toolset group is shared
|
|
24
|
+
* by every per-request server — an in-place extension would be re-applied on
|
|
25
|
+
* every request, against a definition other requests are reading concurrently.
|
|
26
|
+
*/
|
|
27
|
+
export declare function composeToolHandler(tool: ToolDefinition<any, any>, options: ComposeOptions): {
|
|
28
|
+
schema: z.ZodObject<any, z.core.$strip>;
|
|
29
|
+
handler: (input: ComposedInput, extra: import("@modelcontextprotocol/server").ServerContext) => Promise<import("@modelcontextprotocol/server").CallToolResult>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -6,25 +6,37 @@ import { wrapWithTokenLimit } from '../transformers/wrapWithTokenLimit.js';
|
|
|
6
6
|
import { wrapWithToolResult } from '../transformers/wrapWithToolResult.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { generateFieldsDescription } from '../../utils/generateFieldsDescription.js';
|
|
9
|
+
/**
|
|
10
|
+
* Builds the schema and handler a tool is registered with.
|
|
11
|
+
*
|
|
12
|
+
* The returned schema is a fresh object: the tool definition is never mutated.
|
|
13
|
+
* That matters under the stateless HTTP model, where one toolset group is shared
|
|
14
|
+
* by every per-request server — an in-place extension would be re-applied on
|
|
15
|
+
* every request, against a definition other requests are reading concurrently.
|
|
16
|
+
*/
|
|
9
17
|
export function composeToolHandler(tool, options) {
|
|
10
|
-
const { useFields, errorHandler, maxTokens } = options;
|
|
18
|
+
const { useFields, errorHandler, maxTokens, useOrganization = false, } = options;
|
|
11
19
|
// Step 1: Add `fields` to schema if needed
|
|
12
20
|
const fieldDesc = useFields
|
|
13
21
|
? generateFieldsDescription(tool.outputSchema, tool.importantFields ?? [], tool.name)
|
|
14
22
|
: undefined;
|
|
15
|
-
|
|
23
|
+
const schema = extendSchema(tool.schema, fieldDesc, useOrganization);
|
|
16
24
|
// Step 2: Compose
|
|
17
25
|
const baseHandler = wrapWithErrorHandling(wrapWithOrganizationContext(tool.handler), errorHandler);
|
|
18
|
-
const
|
|
19
|
-
return
|
|
26
|
+
const composed = useFields ? wrapWithFieldPicking(baseHandler) : baseHandler;
|
|
27
|
+
return {
|
|
28
|
+
schema,
|
|
29
|
+
handler: wrapWithToolResult(wrapWithTokenLimit(composed, maxTokens)),
|
|
30
|
+
};
|
|
20
31
|
}
|
|
21
|
-
function extendSchema(schema, desc) {
|
|
22
|
-
const extension = {
|
|
23
|
-
|
|
32
|
+
function extendSchema(schema, desc, withOrganization = false) {
|
|
33
|
+
const extension = {};
|
|
34
|
+
if (withOrganization) {
|
|
35
|
+
extension.organization = z
|
|
24
36
|
.string()
|
|
25
37
|
.optional()
|
|
26
|
-
.describe('Optional organization name. Use list_organizations to inspect available organizations.')
|
|
27
|
-
}
|
|
38
|
+
.describe('Optional organization name. Use list_organizations to inspect available organizations.');
|
|
39
|
+
}
|
|
28
40
|
if (desc) {
|
|
29
41
|
extension.fields = z.string().describe(desc);
|
|
30
42
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CallToolResult, ServerContext } from '@modelcontextprotocol/server';
|
|
2
|
+
import { SafeResult } from '../../types/result.js';
|
|
3
|
+
/**
|
|
4
|
+
* Convert SafeResult<T> to CallToolResult
|
|
5
|
+
*/
|
|
6
|
+
export declare function wrapWithToolResult<I, T>(fn: (input: I) => Promise<SafeResult<string | T>>): (input: I, extra: ServerContext) => Promise<CallToolResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import type { BacklogOAuthConfig } from './auth/backlogOAuthConfig.js';
|
|
3
|
+
import type { TokenStore } from './auth/tokenStore.js';
|
|
4
|
+
import type { BacklogMCPServer } from './utils/wrapServerWithToolRegistry.js';
|
|
5
|
+
type RunHttpMcpServerOptions = {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
path: string;
|
|
9
|
+
version: string;
|
|
10
|
+
enableJsonResponse: boolean;
|
|
11
|
+
allowedHosts?: string[];
|
|
12
|
+
allowedOrigins?: string[];
|
|
13
|
+
createServer: () => BacklogMCPServer;
|
|
14
|
+
oauthConfig?: BacklogOAuthConfig;
|
|
15
|
+
tokenStore?: TokenStore;
|
|
16
|
+
};
|
|
17
|
+
type HttpMcpServerHandle = {
|
|
18
|
+
httpServer: Server;
|
|
19
|
+
shutdown: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export declare const runHttpMcpServer: (options: RunHttpMcpServerOptions) => Promise<HttpMcpServerHandle>;
|
|
22
|
+
export {};
|
package/build/index.d.ts
ADDED
package/build/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { hideBin } from 'yargs/helpers';
|
|
|
8
8
|
import { getBacklogOAuthConfig } from './auth/backlogOAuthConfig.js';
|
|
9
9
|
import { createTokenStore } from './auth/tokenStore.js';
|
|
10
10
|
import { createTranslationHelper } from './createTranslationHelper.js';
|
|
11
|
+
import { loadTranslationOverrides } from './loadTranslationOverrides.js';
|
|
11
12
|
import { createBacklogMcpServer } from './createBacklogMcpServer.js';
|
|
12
13
|
import { runHttpMcpServer } from './httpMcpServer.js';
|
|
13
14
|
import { createBacklogClientRegistry, createOAuthBacklogClientRegistry, } from './utils/backlogClientRegistry.js';
|
|
@@ -131,13 +132,18 @@ if (tokenStore) {
|
|
|
131
132
|
cleanupTimer.unref();
|
|
132
133
|
}
|
|
133
134
|
const useFields = argv.optimizeResponse;
|
|
134
|
-
const transHelper = createTranslationHelper();
|
|
135
|
+
const transHelper = createTranslationHelper(loadTranslationOverrides());
|
|
135
136
|
const maxTokens = argv.maxTokens;
|
|
136
137
|
const prefix = argv.prefix;
|
|
137
138
|
const enabledToolsets = argv.dynamicToolsets
|
|
138
139
|
? argv.enableToolsets.filter((a) => a !== 'all')
|
|
139
140
|
: argv.enableToolsets;
|
|
140
|
-
const mcpOption = {
|
|
141
|
+
const mcpOption = {
|
|
142
|
+
useFields: useFields,
|
|
143
|
+
maxTokens,
|
|
144
|
+
prefix,
|
|
145
|
+
useOrganization: clientRegistry.isMultiOrganization,
|
|
146
|
+
};
|
|
141
147
|
// Built once and shared by every server the factory produces. `enable_toolset`
|
|
142
148
|
// mutates this group, and the stateless HTTP model discards its server after
|
|
143
149
|
// each request — so a per-server group would lose the enablement immediately.
|
package/build/lib.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library entry point.
|
|
3
|
+
*
|
|
4
|
+
* `src/index.ts` is the CLI: it parses argv, reads config files and starts a
|
|
5
|
+
* transport. Consumers that only want the tool layer — to host the same tools on
|
|
6
|
+
* a different runtime, for example — cannot import it without running all of that.
|
|
7
|
+
* This module exposes the pieces needed to build a server, and nothing that runs
|
|
8
|
+
* on import.
|
|
9
|
+
*
|
|
10
|
+
* Nothing reachable from here may touch a Node built-in. `loadTranslationOverrides`
|
|
11
|
+
* is the counter-example worth remembering: it reads the override file from disk,
|
|
12
|
+
* so it belongs to the CLI and is deliberately absent below. Consumers on other
|
|
13
|
+
* runtimes pass their own overrides to `createTranslationHelper`.
|
|
14
|
+
*/
|
|
15
|
+
export { allTools } from './tools/tools.js';
|
|
16
|
+
export { composeToolHandler } from './handlers/builders/composeToolHandler.js';
|
|
17
|
+
export { createTranslationHelper } from './createTranslationHelper.js';
|
|
18
|
+
export { backlogErrorHandler } from './backlog/backlogErrorHandler.js';
|
|
19
|
+
export { buildToolSchema } from './types/tool.js';
|
|
20
|
+
export { isErrorLike } from './types/result.js';
|
|
21
|
+
export type { ComposeOptions } from './handlers/builders/composeToolHandler.js';
|
|
22
|
+
export type { TranslationHelper } from './createTranslationHelper.js';
|
|
23
|
+
export type { ToolDefinition, DynamicToolDefinition, ToolRegistrar, } from './types/tool.js';
|
|
24
|
+
export type { Toolset, ToolsetGroup, DynamicToolset, DynamicToolsetGroup, } from './types/toolsets.js';
|
|
25
|
+
export type { ErrorLike, SafeResult } from './types/result.js';
|
package/build/lib.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library entry point.
|
|
3
|
+
*
|
|
4
|
+
* `src/index.ts` is the CLI: it parses argv, reads config files and starts a
|
|
5
|
+
* transport. Consumers that only want the tool layer — to host the same tools on
|
|
6
|
+
* a different runtime, for example — cannot import it without running all of that.
|
|
7
|
+
* This module exposes the pieces needed to build a server, and nothing that runs
|
|
8
|
+
* on import.
|
|
9
|
+
*
|
|
10
|
+
* Nothing reachable from here may touch a Node built-in. `loadTranslationOverrides`
|
|
11
|
+
* is the counter-example worth remembering: it reads the override file from disk,
|
|
12
|
+
* so it belongs to the CLI and is deliberately absent below. Consumers on other
|
|
13
|
+
* runtimes pass their own overrides to `createTranslationHelper`.
|
|
14
|
+
*/
|
|
15
|
+
export { allTools } from './tools/tools.js';
|
|
16
|
+
export { composeToolHandler } from './handlers/builders/composeToolHandler.js';
|
|
17
|
+
export { createTranslationHelper } from './createTranslationHelper.js';
|
|
18
|
+
export { backlogErrorHandler } from './backlog/backlogErrorHandler.js';
|
|
19
|
+
export { buildToolSchema } from './types/tool.js';
|
|
20
|
+
export { isErrorLike } from './types/result.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads description overrides from a `.backlog-mcp-serverrc` file (`.json`,
|
|
3
|
+
* `.yaml` or `.yml`) in the user's home directory.
|
|
4
|
+
*
|
|
5
|
+
* Node-only, and kept separate from `createTranslationHelper` for that reason:
|
|
6
|
+
* cosmiconfig walks the filesystem and the default search path is the home
|
|
7
|
+
* directory. The CLI calls this and hands the result to the helper.
|
|
8
|
+
*
|
|
9
|
+
* The file is user-authored, so its contents are unknown: anything that is not a
|
|
10
|
+
* string is dropped here rather than passed on. Every override ends up in a tool
|
|
11
|
+
* description, and a number or an array there would produce an invalid
|
|
12
|
+
* `tools/list` payload.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadTranslationOverrides(options?: {
|
|
15
|
+
configName?: string;
|
|
16
|
+
searchDir?: string;
|
|
17
|
+
}): Record<string, string>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cosmiconfigSync } from 'cosmiconfig';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
/**
|
|
4
|
+
* Reads description overrides from a `.backlog-mcp-serverrc` file (`.json`,
|
|
5
|
+
* `.yaml` or `.yml`) in the user's home directory.
|
|
6
|
+
*
|
|
7
|
+
* Node-only, and kept separate from `createTranslationHelper` for that reason:
|
|
8
|
+
* cosmiconfig walks the filesystem and the default search path is the home
|
|
9
|
+
* directory. The CLI calls this and hands the result to the helper.
|
|
10
|
+
*
|
|
11
|
+
* The file is user-authored, so its contents are unknown: anything that is not a
|
|
12
|
+
* string is dropped here rather than passed on. Every override ends up in a tool
|
|
13
|
+
* description, and a number or an array there would produce an invalid
|
|
14
|
+
* `tools/list` payload.
|
|
15
|
+
*/
|
|
16
|
+
export function loadTranslationOverrides(options) {
|
|
17
|
+
const explorer = cosmiconfigSync(options?.configName ?? 'backlog-mcp-server');
|
|
18
|
+
const searchPath = options?.searchDir ?? os.homedir();
|
|
19
|
+
const config = explorer.search(searchPath)?.config;
|
|
20
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => typeof value === 'string'));
|
|
24
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { MCPOptions } from './types/mcp.js';
|
|
2
|
+
import { DynamicToolsetGroup, ToolsetGroup } from './types/toolsets.js';
|
|
3
|
+
import { BacklogMCPServer } from './utils/wrapServerWithToolRegistry.js';
|
|
4
|
+
export declare function registerTools(server: BacklogMCPServer, toolsetGroup: ToolsetGroup, options: MCPOptions): void;
|
|
5
|
+
export declare function registerDynamicTools(server: BacklogMCPServer, dynamicToolsetGroup: DynamicToolsetGroup, prefix: string): void;
|
package/build/registerTools.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { backlogErrorHandler } from './backlog/backlogErrorHandler.js';
|
|
2
2
|
import { composeToolHandler } from './handlers/builders/composeToolHandler.js';
|
|
3
3
|
export function registerTools(server, toolsetGroup, options) {
|
|
4
|
-
const { useFields, maxTokens, prefix } = options;
|
|
4
|
+
const { useFields, maxTokens, prefix, useOrganization } = options;
|
|
5
5
|
registerToolsets({
|
|
6
6
|
server,
|
|
7
7
|
toolsetGroup,
|
|
8
8
|
prefix,
|
|
9
|
-
|
|
9
|
+
prepareTool: (tool) =>
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
11
|
composeToolHandler(tool, {
|
|
12
12
|
useFields,
|
|
13
13
|
errorHandler: backlogErrorHandler,
|
|
14
14
|
maxTokens,
|
|
15
|
+
useOrganization,
|
|
15
16
|
}),
|
|
16
17
|
});
|
|
17
18
|
}
|
|
@@ -20,18 +21,18 @@ export function registerDynamicTools(server, dynamicToolsetGroup, prefix) {
|
|
|
20
21
|
server,
|
|
21
22
|
toolsetGroup: dynamicToolsetGroup,
|
|
22
23
|
prefix,
|
|
23
|
-
|
|
24
|
+
prepareTool: (tool) => ({ schema: tool.schema, handler: tool.handler }),
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
|
-
function registerToolsets({ server, toolsetGroup, prefix,
|
|
27
|
+
function registerToolsets({ server, toolsetGroup, prefix, prepareTool, }) {
|
|
27
28
|
for (const toolset of toolsetGroup.toolsets) {
|
|
28
29
|
if (!toolset.enabled) {
|
|
29
30
|
continue;
|
|
30
31
|
}
|
|
31
32
|
for (const tool of toolset.tools) {
|
|
32
33
|
const toolNameWithPrefix = `${prefix}${tool.name}`;
|
|
33
|
-
const handler =
|
|
34
|
-
server.registerOnce(toolNameWithPrefix, tool.description,
|
|
34
|
+
const { schema, handler } = prepareTool(tool);
|
|
35
|
+
server.registerOnce(toolNameWithPrefix, tool.description, schema, handler);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Backlog } from 'backlog-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { TranslationHelper } from '../createTranslationHelper.js';
|
|
4
|
+
import { DocumentItemSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
5
|
+
import { ToolDefinition } from '../types/tool.js';
|
|
6
|
+
declare const addDocumentSchema: (t: TranslationHelper["t"]) => {
|
|
7
|
+
projectId: z.ZodNumber;
|
|
8
|
+
title: z.ZodOptional<z.ZodString>;
|
|
9
|
+
content: z.ZodOptional<z.ZodString>;
|
|
10
|
+
emoji: z.ZodOptional<z.ZodString>;
|
|
11
|
+
parentId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
addLast: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
};
|
|
14
|
+
export declare const addDocumentTool: (backlog: Backlog, { t }: TranslationHelper) => ToolDefinition<ReturnType<typeof addDocumentSchema>, (typeof DocumentItemSchema)["shape"]>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Backlog } from 'backlog-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { TranslationHelper } from '../createTranslationHelper.js';
|
|
4
|
+
import { IssueSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
5
|
+
import { ToolDefinition } from '../types/tool.js';
|
|
6
|
+
declare const addIssueSchema: (t: TranslationHelper["t"]) => {
|
|
7
|
+
projectId: z.ZodNumber;
|
|
8
|
+
summary: z.ZodString;
|
|
9
|
+
issueTypeId: z.ZodNumber;
|
|
10
|
+
priorityId: z.ZodNumber;
|
|
11
|
+
description: z.ZodOptional<z.ZodString>;
|
|
12
|
+
startDate: z.ZodOptional<z.ZodString>;
|
|
13
|
+
dueDate: z.ZodOptional<z.ZodString>;
|
|
14
|
+
estimatedHours: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
actualHours: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
categoryId: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
17
|
+
versionId: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
18
|
+
milestoneId: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
19
|
+
assigneeId: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
notifiedUserId: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
21
|
+
attachmentId: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
22
|
+
parentIssueId: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
customFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
24
|
+
id: z.ZodNumber;
|
|
25
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodArray<z.ZodString>, z.ZodArray<z.ZodNumber>]>>;
|
|
26
|
+
otherValue: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, z.core.$strip>>>;
|
|
28
|
+
};
|
|
29
|
+
export declare const addIssueTool: (backlog: Backlog, { t }: TranslationHelper) => ToolDefinition<ReturnType<typeof addIssueSchema>, (typeof IssueSchema)["shape"]>;
|
|
30
|
+
export {};
|