Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"rules": {
"@typescript-eslint/ban-types": "off",
"import/no-unresolved": "off",
"import/no-relative-packages": "error"
},
"overrides": [
Expand Down
7 changes: 7 additions & 0 deletions core/controller-decorator/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './src/impl/http/HTTPControllerMetaBuilder';
import './src/impl/mcp/MCPControllerMetaBuilder';

export * from '@eggjs/tegg-types/controller-decorator';
export * from './src/model';
Expand All @@ -9,8 +10,14 @@ export * from './src/decorator/http/HTTPController';
export * from './src/decorator/http/HTTPMethod';
export * from './src/decorator/http/HTTPParam';
export * from './src/decorator/http/Host';
export * from './src/decorator/mcp/MCPController';
export * from './src/decorator/mcp/MCPPrompt';
export * from './src/decorator/mcp/MCPResource';
export * from './src/decorator/mcp/MCPTool';
export * from './src/decorator/mcp/Extra';
export * from './src/builder/ControllerMetaBuilderFactory';
export * from './src/util/ControllerMetadataUtil';
export * from './src/util/MCPInfoUtil';
export * from './src/util/HTTPPriorityUtil';

export { default as ControllerInfoUtil } from './src/util/ControllerInfoUtil';
1 change: 1 addition & 0 deletions core/controller-decorator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@eggjs/tegg-common-util": "^3.53.0",
"@eggjs/tegg-metadata": "^3.53.0",
"@eggjs/tegg-types": "^3.53.0",
"@modelcontextprotocol/sdk": "^1.10.0",
"is-type-of": "^1.2.1",
"path-to-regexp": "^1.8.0",
"reflect-metadata": "^0.1.13",
Expand Down
16 changes: 16 additions & 0 deletions core/controller-decorator/src/decorator/mcp/Extra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
EggProtoImplClass,
} from '@eggjs/tegg-types';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';

export function Extra() {
return function(
target: any,
propertyKey: PropertyKey,
parameterIndex: number,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MCPInfoUtil.setMCPExtra(parameterIndex, controllerClazz, methodName);
};
}
33 changes: 33 additions & 0 deletions core/controller-decorator/src/decorator/mcp/MCPController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ControllerType, MCPControllerParams, AccessLevel, EggProtoImplClass } from '@eggjs/tegg-types';
import { PrototypeUtil, SingletonProto } from '@eggjs/core-decorator';
import ControllerInfoUtil from '../../util/ControllerInfoUtil';
import { StackUtil } from '@eggjs/tegg-common-util';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';

export function MCPController(param?: MCPControllerParams) {
return function(constructor: EggProtoImplClass) {
const func = SingletonProto({
accessLevel: AccessLevel.PUBLIC,
name: param?.protoName,
});
func(constructor);
ControllerInfoUtil.setControllerType(constructor, ControllerType.MCP);
if (param?.controllerName) {
ControllerInfoUtil.setControllerName(constructor, param?.controllerName);
}
// './tegg/core/common-util/src/StackUtil.ts',
// './tegg/core/core-decorator/src/decorator/Prototype.ts',
// './tegg/core/controller-decorator/src/decorator/tr/TRController.ts',
// './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',
// './tegg/core/core-decorator/node_modules/_reflect-metadata@0.1.13@reflect-metadata/Reflect.js',
// './tegg/core/controller-decorator/test/fixtures/TRFooController.ts',
PrototypeUtil.setFilePath(constructor, StackUtil.getCalleeFromStack(false, 5));

if (param?.name) {
MCPInfoUtil.setMCPName(param.name, constructor);
}
if (param?.version) {
MCPInfoUtil.setMCPVersion(param.version, constructor);
}
};
}
52 changes: 52 additions & 0 deletions core/controller-decorator/src/decorator/mcp/MCPPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
ControllerType,
EggProtoImplClass,
MCPPromptParams,
} from '@eggjs/tegg-types';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';
import MethodInfoUtil from '../../../src/util/MethodInfoUtil';

export function MCPPrompt(params?: MCPPromptParams) {
return function(
target: any,
propertyKey: PropertyKey,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MethodInfoUtil.setMethodControllerType(
controllerClazz,
methodName,
ControllerType.MCP,
);
MCPInfoUtil.setMCPPromptParams(
{
...params,
mcpName: params?.name,
name: methodName,
},
controllerClazz,
methodName,
);
MCPInfoUtil.setMCPPrompt(controllerClazz, methodName);
};
}

export function PromptArgsSchema(argsSchema: Parameters<McpServer['prompt']>['2']) {
return function(
target: any,
propertyKey: PropertyKey,
parameterIndex: number,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MCPInfoUtil.setMCPPromptArgsInArgs(
{
argsSchema,
index: parameterIndex,
},
controllerClazz,
methodName,
);
};
}
32 changes: 32 additions & 0 deletions core/controller-decorator/src/decorator/mcp/MCPResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
ControllerType,
EggProtoImplClass,
MCPResourceParams,
} from '@eggjs/tegg-types';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';
import MethodInfoUtil from '../../../src/util/MethodInfoUtil';

export function MCPResource(params: MCPResourceParams) {
return function(
target: any,
propertyKey: PropertyKey,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MethodInfoUtil.setMethodControllerType(
controllerClazz,
methodName,
ControllerType.MCP,
);
MCPInfoUtil.setMCPResourceParams(
{
...params,
mcpName: params.name,
name: methodName,
},
controllerClazz,
methodName,
);
MCPInfoUtil.setMCPResource(controllerClazz, methodName);
};
}
52 changes: 52 additions & 0 deletions core/controller-decorator/src/decorator/mcp/MCPTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
ControllerType,
EggProtoImplClass,
MCPToolParams,
} from '@eggjs/tegg-types';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';
import MethodInfoUtil from '../../../src/util/MethodInfoUtil';

export function MCPTool(params?: MCPToolParams) {
return function(
target: any,
propertyKey: PropertyKey,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MethodInfoUtil.setMethodControllerType(
controllerClazz,
methodName,
ControllerType.MCP,
);
MCPInfoUtil.setMCPToolParams(
{
...params,
mcpName: params?.name,
name: methodName,
},
controllerClazz,
methodName,
);
MCPInfoUtil.setMCPTool(controllerClazz, methodName);
};
}

export function ToolArgsSchema(argsSchema: Parameters<McpServer['tool']>['2']) {
return function(
target: any,
propertyKey: PropertyKey,
parameterIndex: number,
) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
MCPInfoUtil.setMCPToolArgsInArgs(
{
argsSchema,
index: parameterIndex,
},
controllerClazz,
methodName,
);
};
}
87 changes: 87 additions & 0 deletions core/controller-decorator/src/impl/mcp/MCPControllerMetaBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import assert from 'assert';
import { PrototypeUtil } from '@eggjs/core-decorator';
import { MCPPromptMeta, MCPResourceMeta, MCPToolMeta } from '../../model';
import { ControllerValidator } from '../../util/validator/ControllerValidator';
import ControllerInfoUtil from '../../util/ControllerInfoUtil';
import { MCPControllerPromptMetaBuilder } from './MCPControllerPromptMetaBuilder';
import { MCPControllerResourceMetaBuilder } from './MCPControllerResourceMetaBuilder';
import { MCPControllerToolMetaBuilder } from './MCPControllerToolMetaBuilder';
import { MCPInfoUtil } from '../../../src/util/MCPInfoUtil';
import { ControllerType, EggProtoImplClass } from '@eggjs/tegg-types';
import { MCPControllerMeta } from '../../../src/model/MCPControllerMeta';
import { ControllerMetaBuilderFactory } from '../../builder/ControllerMetaBuilderFactory';

export class MCPControllerMetaBuilder {
private readonly clazz: EggProtoImplClass;

constructor(clazz: EggProtoImplClass) {
this.clazz = clazz;
}

private buildResource(): MCPResourceMeta[] {
const methodNames = MCPInfoUtil.getMCPResource(this.clazz);
const methods: MCPResourceMeta[] = [];
for (const methodName of methodNames) {
const builder = new MCPControllerResourceMetaBuilder(this.clazz, methodName);
const methodMeta = builder.build();
if (methodMeta) {
methods.push(methodMeta);
}
}
return methods;
}

private buildPrompt(): MCPPromptMeta[] {
const methodNames = MCPInfoUtil.getMCPPrompt(this.clazz);
const methods: MCPPromptMeta[] = [];
for (const methodName of methodNames) {
const builder = new MCPControllerPromptMetaBuilder(this.clazz, methodName);
const methodMeta = builder.build();
if (methodMeta) {
methods.push(methodMeta);
}
}
return methods;
}

private buildTool(): MCPToolMeta[] {
const methodNames = MCPInfoUtil.getMCPTool(this.clazz);
const methods: MCPToolMeta[] = [];
for (const methodName of methodNames) {
const builder = new MCPControllerToolMetaBuilder(this.clazz, methodName);
const methodMeta = builder.build();
if (methodMeta) {
methods.push(methodMeta);
}
}
return methods;
}

build(): MCPControllerMeta {
ControllerValidator.validate(this.clazz);
const controllerType = ControllerInfoUtil.getControllerType(this.clazz);
assert(controllerType === ControllerType.MCP, 'invalidate controller type');
const mcpMiddlewares = ControllerInfoUtil.getControllerMiddlewares(this.clazz);
const resources = this.buildResource();
const prompts = this.buildPrompt();
const tools = this.buildTool();
const property = PrototypeUtil.getProperty(this.clazz);
const protoName = property!.name as string;
const clazzName = this.clazz.name;
const controllerName = ControllerInfoUtil.getControllerName(this.clazz) || clazzName;
const name = MCPInfoUtil.getMCPName(this.clazz) || controllerName;
const version = MCPInfoUtil.getMCPVersion(this.clazz) || '1.0.0';
const needAcl = ControllerInfoUtil.hasControllerAcl(this.clazz);
const aclCode = ControllerInfoUtil.getControllerAcl(this.clazz);
return new MCPControllerMeta(
clazzName, protoName, controllerName, name, version, tools, resources,
prompts, mcpMiddlewares, needAcl, aclCode,
);
}

static create(clazz: EggProtoImplClass) {
return new MCPControllerMetaBuilder(clazz);
}
}

ControllerMetaBuilderFactory.registerControllerMetaBuilder(ControllerType.MCP, MCPControllerMetaBuilder.create);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { EggProtoImplClass } from '@eggjs/tegg-types';
import { MCPPromptMeta } from '../../model';
import { MethodValidator } from '../../util/validator/MethodValidator';
import MethodInfoUtil from '../../util/MethodInfoUtil';
import { MCPInfoUtil } from '../../util/MCPInfoUtil';

export class MCPControllerPromptMetaBuilder {
private readonly clazz: EggProtoImplClass;
private readonly methodName: string;

constructor(clazz: EggProtoImplClass, methodName: string) {
this.clazz = clazz;
this.methodName = methodName;
}

build(): MCPPromptMeta | undefined {
MethodValidator.validate(this.clazz, this.methodName);
const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);
if (!controllerType) {
return undefined;
}
const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);
const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);
const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);
const params = MCPInfoUtil.getMCPPromptParams(this.clazz, this.methodName);
const detail = MCPInfoUtil.getMCPPromptArgsIndex(this.clazz, this.methodName);
const extra = MCPInfoUtil.getMCPExtra(this.clazz, this.methodName);

return new MCPPromptMeta({
name: this.methodName,
middlewares,
needAcl,
aclCode,
detail,
extra,
...params,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { EggProtoImplClass } from '@eggjs/tegg-types';
import { MCPResourceMeta } from '../../model';
import { MethodValidator } from '../../util/validator/MethodValidator';
import MethodInfoUtil from '../../util/MethodInfoUtil';
import { MCPInfoUtil } from '../../util/MCPInfoUtil';

export class MCPControllerResourceMetaBuilder {
private readonly clazz: EggProtoImplClass;
private readonly methodName: string;

constructor(clazz: EggProtoImplClass, methodName: string) {
this.clazz = clazz;
this.methodName = methodName;
}

build(): MCPResourceMeta | undefined {
MethodValidator.validate(this.clazz, this.methodName);
const controllerType = MethodInfoUtil.getMethodControllerType(this.clazz, this.methodName);
if (!controllerType) {
return undefined;
}
const middlewares = MethodInfoUtil.getMethodMiddlewares(this.clazz, this.methodName);
const needAcl = MethodInfoUtil.hasMethodAcl(this.clazz, this.methodName);
const aclCode = MethodInfoUtil.getMethodAcl(this.clazz, this.methodName);
const params = MCPInfoUtil.getMCPResourceParams(this.clazz, this.methodName);
const extra = MCPInfoUtil.getMCPExtra(this.clazz, this.methodName);

return new MCPResourceMeta({
name: this.methodName,
middlewares,
needAcl,
aclCode,
extra,
...params,
});
}
}
Loading
Loading