mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
2d0eca7938
Nine *.mcp.ts files reached through addons.bridge for one boolean each, and platform.routes plus the legacy registrars still do. That was never a preference: a decorator's options object is built while the class is being defined, so the `when:` closure had no `this` to reach an injected service through, and the only thing in scope was a module-level singleton that builds its own AddonsService outside the container. Nine domains hid an edge to the addons domain from the module graph to answer "is this addon on". @trek/nest-mcp hands the gate its declaring instance now, resolved at attach rather than captured at module load, and addons/addon-gate.ts turns that into addonGate(ADDON_IDS.X) over the injected service. The option is declared with method syntax so a predicate written against its own controller class stays assignable. Two more edges went the same way, both checked for cycles first: reservations.mcp injects AssignmentsService (AssignmentsModule never reaches back), atlas.mcp and journey.mcp inject AuthService. The tests that forced a toggle by mocking the bridge module now write the addon row the admin panel writes, so they exercise the real read. Four in-container uses stay, each documented in the nest README: places.mcp (a real Days -> Places -> Assignments cycle), budget/packing/costs against trips.bridge, user-cleanup against budget.bridge, and the two multer interceptor options, whose fix is MulterModule.registerAsync.
165 lines
6.1 KiB
TypeScript
165 lines
6.1 KiB
TypeScript
import type { ZodRawShape } from 'zod';
|
|
|
|
/**
|
|
* The per-session context handed to `McpRegistry.attach(server, ctx)` and
|
|
* forwarded to every handler (last argument) and access predicate/policy.
|
|
*
|
|
* The package defines NO context fields of its own — the host application
|
|
* augments this interface once:
|
|
*
|
|
* declare module '@trek/nest-mcp' {
|
|
* interface McpContext { userId: number; scopes: string[] | null }
|
|
* }
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
export interface McpContext {}
|
|
|
|
/**
|
|
* Host-augmentable registry of valid `access.group` values — the `McpContext`
|
|
* augmentation trick applied to groups. Augment once:
|
|
*
|
|
* declare module '@trek/nest-mcp' {
|
|
* interface McpAccessGroupRegistry extends Record<MyGroupUnion, true> {}
|
|
* }
|
|
*
|
|
* Unaugmented, `McpAccessGroup` stays `string` — the package attaches no
|
|
* semantics to groups, same contract as `McpContext`.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
export interface McpAccessGroupRegistry {}
|
|
|
|
export type McpAccessGroup = keyof McpAccessGroupRegistry extends never
|
|
? string
|
|
: keyof McpAccessGroupRegistry & string;
|
|
|
|
/**
|
|
* Host augmentation point for the access *mode*, mirroring
|
|
* `McpAccessGroupRegistry`:
|
|
*
|
|
* declare module '@trek/nest-mcp' {
|
|
* interface McpAccessModeRegistry extends Record<MyModeUnion, true> {}
|
|
* }
|
|
*
|
|
* Unaugmented, `McpAccessMode` falls back to `'read' | 'write'` — the two
|
|
* modes every host had before this was augmentable, so leaving the registry
|
|
* empty keeps the previous contract exactly.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
export interface McpAccessModeRegistry {}
|
|
|
|
export type McpAccessMode = keyof McpAccessModeRegistry extends never
|
|
? 'read' | 'write'
|
|
: keyof McpAccessModeRegistry & string;
|
|
|
|
/**
|
|
* Declarative access marker resolved by the host-supplied `accessPolicy`.
|
|
* The package attaches no semantics to `group`/`mode` — the policy does.
|
|
*/
|
|
export interface McpDeclarativeAccess {
|
|
group: McpAccessGroup;
|
|
mode: McpAccessMode;
|
|
}
|
|
|
|
/** Predicate escape hatch — bypasses the policy entirely. */
|
|
export type McpAccessPredicate = (ctx: McpContext) => boolean;
|
|
|
|
export type McpAccess = McpDeclarativeAccess | McpAccessPredicate;
|
|
|
|
/**
|
|
* Host-supplied resolver for declarative access, given once at
|
|
* `McpModule.forRoot({ accessPolicy })`. Return true to register the entry
|
|
* on the session's server.
|
|
*/
|
|
export type McpAccessPolicy = (access: McpDeclarativeAccess, ctx: McpContext) => boolean;
|
|
|
|
interface McpEntryOptionsBase {
|
|
name: string;
|
|
title?: string;
|
|
description?: string;
|
|
/**
|
|
* Availability gate evaluated BEFORE `access` (short-circuits it) — for
|
|
* "is this feature on at all" checks like addon toggles. Keeps `access`
|
|
* purely about permissions, so declarative scope markers still compose
|
|
* with runtime feature gates instead of being replaced by predicates.
|
|
*
|
|
* `self` is the `@McpController()` instance the entry was declared on,
|
|
* handed in at attach time. Without it the gate could only reach a
|
|
* module-level singleton, because the options object is built when the class
|
|
* is defined and there is no `this` yet — which is how a host ends up
|
|
* constructing a second copy of a service outside its own container just to
|
|
* answer "is this addon on". With it, the predicate reads an injected
|
|
* collaborator and the dependency is visible in the module graph.
|
|
*
|
|
* Declared with method syntax on purpose: that makes the parameter bivariant,
|
|
* so a predicate written against its own controller class stays assignable
|
|
* here. The registry always passes the instance that declared the entry.
|
|
*/
|
|
when?(ctx: McpContext, self: object): boolean;
|
|
/** Omitted ⇒ the entry is always registered (subject to `when`). */
|
|
access?: McpAccess;
|
|
}
|
|
|
|
/**
|
|
* Mirrors the SDK's `registerTool` config. `inputSchema` is a ZodRawShape
|
|
* passed straight through — the SDK does the parsing/validation.
|
|
* `annotations`/`_meta` are kept structural so the package's public d.ts
|
|
* never imports deep SDK subpaths.
|
|
*/
|
|
export interface ToolOptions extends McpEntryOptionsBase {
|
|
inputSchema?: ZodRawShape;
|
|
outputSchema?: ZodRawShape;
|
|
annotations?: Record<string, unknown>;
|
|
_meta?: Record<string, unknown>;
|
|
}
|
|
|
|
/** Mirrors `registerResource(name, uri, metadata, cb)` for a fixed URI. */
|
|
export interface ResourceOptions extends McpEntryOptionsBase {
|
|
uri: string;
|
|
mimeType?: string;
|
|
_meta?: Record<string, unknown>;
|
|
}
|
|
|
|
/** Mirrors `registerResource(name, new ResourceTemplate(uriTemplate), metadata, cb)`. */
|
|
export interface ResourceTemplateOptions extends McpEntryOptionsBase {
|
|
uriTemplate: string;
|
|
mimeType?: string;
|
|
_meta?: Record<string, unknown>;
|
|
}
|
|
|
|
/** Mirrors the SDK's `registerPrompt` config (argsSchema entries must be string-valued Zod schemas). */
|
|
export interface PromptOptions extends McpEntryOptionsBase {
|
|
argsSchema?: ZodRawShape;
|
|
}
|
|
|
|
export type McpEntryKind = 'tool' | 'resource' | 'resourceTemplate' | 'prompt';
|
|
|
|
export type McpEntry =
|
|
| { kind: 'tool'; methodName: string; options: ToolOptions }
|
|
| { kind: 'resource'; methodName: string; options: ResourceOptions }
|
|
| { kind: 'resourceTemplate'; methodName: string; options: ResourceTemplateOptions }
|
|
| { kind: 'prompt'; methodName: string; options: PromptOptions };
|
|
|
|
/** Introspection view of one recorded entry (see `McpRegistry.list()`). */
|
|
export interface McpRegistryListing {
|
|
kind: McpEntryKind;
|
|
name: string;
|
|
className: string;
|
|
methodName: string;
|
|
access?: McpAccess;
|
|
}
|
|
|
|
/**
|
|
* Host-supplied per-entry check run by `validate()`. Return a problem
|
|
* description to fail boot, null/undefined to accept. Only called for
|
|
* entries with DECLARATIVE access (predicates are opaque to the host).
|
|
*/
|
|
export type McpAccessValidator = (access: McpDeclarativeAccess, entry: McpRegistryListing) => string | null | undefined;
|
|
|
|
export interface McpModuleOptions {
|
|
accessPolicy?: McpAccessPolicy;
|
|
validateAccess?: McpAccessValidator;
|
|
}
|
|
|
|
/** Injection token for the options object given to `McpModule.forRoot()`. */
|
|
export const MCP_MODULE_OPTIONS = Symbol('MCP_MODULE_OPTIONS');
|