Skip to content

Commit 750180e

Browse files
committed
feat: implement tool metadata handling and validation, refactor related components
1 parent 6cfb699 commit 750180e

26 files changed

+384
-125
lines changed

.eslintignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist
2+
.yarn
3+
node_modules
4+
coverage

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"author": "Robert Lindley",
23
"dependencies": {
34
"@backstage/catalog-client": "^1.9.1",
45
"@backstage/catalog-model": "^1.7.3",
@@ -32,8 +33,8 @@
3233
"packageManager": "[email protected]",
3334
"scripts": {
3435
"build": "tsc",
35-
"lint": "eslint . --ext .ts",
36-
"lint:fix": "prettier . --write",
36+
"lint": "eslint 'src/**/*.ts' --ext .ts",
37+
"lint:fix": "prettier . --write && eslint 'src/**/*.ts' --ext .ts --fix",
3738
"start": "node dist/index.js",
3839
"test": "jest --coverage"
3940
},

src/api/backstage-catalog-api.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import {
1818
import { CompoundEntityRef, Entity, stringifyEntityRef } from '@backstage/catalog-model';
1919
import axios, { AxiosInstance, isAxiosError } from 'axios';
2020

21-
import { AuthManager } from '../auth/auth-manager';
2221
import { type AuthConfig } from '../auth';
23-
import { CacheManager } from '../cache/cache-manager';
22+
import { AuthManager } from '../auth/auth-manager';
2423
import { securityAuditor, SecurityEventType } from '../auth/security-auditor';
25-
import { JsonApiDocument, JsonApiFormatter } from '../utils/jsonapi-formatter';
24+
import { CacheManager } from '../cache/cache-manager';
2625
import { logger } from '../utils';
2726
import { isString } from '../utils/guards';
27+
import { JsonApiDocument, JsonApiFormatter } from '../utils/jsonapi-formatter';
2828
import { PaginationHelper, PaginationParams } from '../utils/pagination-helper';
2929

3030
interface BackstageCatalogApiOptions {

src/decorators/tool.decorator.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import 'reflect-metadata';
22

3-
import { ToolMetadata } from '../types';
3+
import { IToolMetadata, ToolClass } from '../types';
44

5-
/**
6-
* Tool class constructor type. We only need to store the constructor as the key
7-
* in the metadata map.
8-
*/
9-
export type ToolClass = new (...args: unknown[]) => unknown;
10-
11-
const toolMetadataMap = new Map<ToolClass, ToolMetadata>();
5+
const toolMetadataMap = new Map<ToolClass, IToolMetadata>();
126

137
export { toolMetadataMap };
148

159
export const TOOL_METADATA_KEY = Symbol('TOOL_METADATA');
1610

17-
export function Tool(metadata: ToolMetadata): ClassDecorator {
11+
export function Tool(metadata: IToolMetadata): ClassDecorator {
1812
return (target) => {
1913
toolMetadataMap.set(target as unknown as ToolClass, metadata);
2014
};

src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { join } from 'path';
55
import { BackstageCatalogApi } from './api/backstage-catalog-api';
66
import { type AuthConfig } from './auth/auth-manager';
77
import { IToolRegistrationContext } from './types';
8+
import { logger } from './utils';
89
import { DefaultToolFactory } from './utils/tool-factory';
910
import { ToolLoader } from './utils/tool-loader';
1011
import { ReflectToolMetadataProvider } from './utils/tool-metadata';
1112
import { DefaultToolRegistrar } from './utils/tool-registrar';
1213
import { DefaultToolValidator } from './utils/tool-validator';
13-
import { logger } from './utils';
1414

1515
export async function startServer(): Promise<void> {
1616
logger.info('Starting Backstage MCP Server');

src/tools/get_entities.tool.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import { BackstageCatalogApi } from '../api/backstage-catalog-api';
66
import { inputSanitizer } from '../auth/input-sanitizer';
77
import { Tool } from '../decorators/tool.decorator';
88
import { ApiStatus, IToolRegistrationContext } from '../types';
9+
import { logger, ToolErrorHandler } from '../utils';
910
import { formatEntityList, FormattedTextResponse, JsonToTextResponse } from '../utils/responses';
10-
import { ToolErrorHandler } from '../utils';
11-
import { logger } from '../utils';
1211

1312
const entityFilterSchema = z.object({
1413
key: z.string(),

src/tools/get_entity_by_ref.tool.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { inputSanitizer } from '../auth/input-sanitizer';
66
import { Tool } from '../decorators/tool.decorator';
77
import { ApiStatus, IToolRegistrationContext } from '../types';
88
import { formatEntity, FormattedTextResponse, ToolErrorHandler } from '../utils';
9-
import { logger } from '../utils';
109

1110
const compoundEntityRefSchema = z.object({
1211
kind: z.string(),

src/tools/get_location_by_ref.tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from 'zod';
44

55
import { Tool } from '../decorators/tool.decorator';
66
import { ApiStatus, IToolRegistrationContext } from '../types';
7-
import { formatLocation, FormattedTextResponse, JsonToTextResponse } from '../utils/responses';
7+
import { formatLocation, FormattedTextResponse } from '../utils/responses';
88
import { ToolErrorHandler } from '../utils/tool-error-handler';
99

1010
const paramsSchema = z.object({

src/types.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
/* eslint-disable import/no-unused-modules */
22
import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
33
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4-
import { z } from 'zod';
54

65
import { BackstageCatalogApi } from './api/backstage-catalog-api';
6+
export type { IToolMetadata, RawToolMetadata } from './types/tool-metadata';
7+
import type { IToolMetadata } from './types/tool-metadata';
8+
9+
export interface ILogger {
10+
debug(message: string, ...args: unknown[]): void;
11+
info(message: string, ...args: unknown[]): void;
12+
warn(message: string, ...args: unknown[]): void;
13+
error(message: string, ...args: unknown[]): void;
14+
fatal(message: string, ...args: unknown[]): void;
15+
}
716

817
export enum ApiStatus {
918
SUCCESS = 'success',
@@ -25,34 +34,30 @@ export interface IToolRegistrationContext {
2534

2635
export type ToolRegistration = (context: IToolRegistrationContext) => RegisteredTool;
2736

28-
export interface ToolMetadata {
29-
name: string;
30-
description: string;
31-
paramsSchema: z.ZodTypeAny;
32-
}
37+
// IToolMetadata is exported from src/types/tool-metadata.ts
3338

34-
export interface Tool {
39+
export interface ITool {
3540
execute(args: unknown, context: object): Promise<CallToolResult>;
3641
}
3742

38-
export interface ToolMetadataProvider {
39-
getMetadata(toolClass: unknown): ToolMetadata | undefined;
43+
export interface IToolMetadataProvider {
44+
getMetadata(toolClass: ToolClass): IToolMetadata | undefined;
4045
}
4146

42-
export interface ToolValidator {
43-
validate(metadata: ToolMetadata, file: string): void;
47+
export interface IToolValidator {
48+
validate(metadata: IToolMetadata, file: string): void;
4449
}
4550

46-
export interface ToolRegistrar {
47-
register(toolClass: ToolConstructor, metadata: ToolMetadata): void;
51+
export interface IToolRegistrar {
52+
register(toolClass: ToolClass, metadata: IToolMetadata): void;
4853
}
4954

50-
export interface ToolFactory {
51-
loadTool(filePath: string): Promise<ToolConstructor | undefined>;
55+
export interface IToolFactory {
56+
loadTool(filePath: string): Promise<IToolConstructor | undefined>;
5257
}
5358

54-
export type ToolConstructor = {
59+
export interface IToolConstructor {
5560
execute(args: unknown, context: object): Promise<CallToolResult>;
56-
};
61+
}
5762

58-
export type ToolClass = ToolConstructor | undefined;
63+
export type ToolClass = unknown;

src/types/tool-metadata.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* RawToolMetadata represents metadata as it appears in a file/manifest
5+
* (paramsSchema is a plain object shape when authored in JSON/JS).
6+
*/
7+
export const rawToolMetadataSchema = z.object({
8+
name: z.string().min(1),
9+
description: z.string().min(1),
10+
paramsSchema: z.record(z.any()).optional(),
11+
});
12+
13+
export type RawToolMetadata = z.infer<typeof rawToolMetadataSchema>;
14+
15+
/**
16+
* IToolMetadata is the runtime form used by the registrar/factory: paramsSchema
17+
* is a Zod schema (z.ZodTypeAny).
18+
*/
19+
export interface IToolMetadata {
20+
name: string;
21+
description: string;
22+
paramsSchema?: z.ZodTypeAny;
23+
}

0 commit comments

Comments
 (0)