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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,4 @@ apps/*/src/**/*.d.ts.map
# Performance test results (generated)
perf-results/
/test-results/.last-run.json
/.claude/planning/
4 changes: 1 addition & 3 deletions libs/sdk/src/__test-utils__/fixtures/plugin.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
*/

import { PluginMetadata } from '../../common/metadata';
import { PluginInterface } from '../../common/interfaces';
import { createProviderMetadata } from './provider.fixtures';
import { createToolMetadata } from './tool.fixtures';

/**
* Creates a simple plugin metadata object
Expand Down Expand Up @@ -61,7 +59,7 @@ export function createPluginWithNestedPlugins(): PluginMetadata {
/**
* Mock plugin class for testing
*/
export class MockPluginClass implements PluginInterface {
export class MockPluginClass {
static readonly metadata: PluginMetadata = {
name: 'MockPlugin',
description: 'A mock plugin',
Expand Down
7 changes: 3 additions & 4 deletions libs/sdk/src/__test-utils__/fixtures/provider.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import 'reflect-metadata';
import { ProviderMetadata, ProviderScope } from '../../common/metadata';
import { ProviderInterface } from '../../common/interfaces';
import { FrontMcpProviderTokens } from '../../common/tokens/provider.tokens';

/**
Expand All @@ -21,7 +20,7 @@ function Injectable() {
* Simple test service class
*/
@Injectable()
export class TestService implements ProviderInterface {
export class TestService {
public readonly name = 'TestService';

constructor() {}
Expand All @@ -35,7 +34,7 @@ export class TestService implements ProviderInterface {
* Service that depends on another service
*/
@Injectable()
export class DependentService implements ProviderInterface {
export class DependentService {
constructor(public readonly testService: TestService) {}

callGreet(): string {
Expand All @@ -47,7 +46,7 @@ export class DependentService implements ProviderInterface {
* Service with async initialization
*/
@Injectable()
export class AsyncService implements ProviderInterface {
export class AsyncService {
private initialized = false;

async with(callback: (service: this) => Promise<void>): Promise<void> {
Expand Down
6 changes: 3 additions & 3 deletions libs/sdk/src/agent/agent.instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ToolInstance } from '../tool/tool.instance';
import { normalizeTool } from '../tool/tool.utils';
import ProviderRegistry from '../provider/provider.registry';
import HookRegistry from '../hooks/hook.registry';
import { Scope } from '../scope';
import { ScopeEntry } from '../common';
import { normalizeHooksFromCls } from '../hooks/hooks.utils';
import { createAdapter, CreateAdapterOptions, ConfigResolver } from './adapters';
import { ConfigService } from '../builtin/config';
Expand Down Expand Up @@ -92,7 +92,7 @@ export class AgentInstance<
Out = AgentOutputOf<{ outputSchema: OutSchema }>,
> extends AgentEntry<InSchema, OutSchema, In, Out> {
private readonly providers: ProviderRegistry;
readonly scope: Scope;
readonly scope: ScopeEntry;
readonly hooks: HookRegistry;

/** The LLM adapter for this agent */
Expand All @@ -119,7 +119,7 @@ export class AgentInstance<
this.id = record.metadata.id ?? record.metadata.name;
this.fullName = this.owner.id + ':' + this.name;
this.scope = this.providers.getActiveScope();
this.hooks = this.scope.providers.getHooksRegistry();
this.hooks = this.scope.hooks;

// inputSchema is always a ZodRawShape
this.inputSchema = (record.metadata.inputSchema ?? {}) as InSchema;
Expand Down
48 changes: 46 additions & 2 deletions libs/sdk/src/agent/agent.scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class AgentScope {
readonly logger: FrontMcpLogger;
readonly ready: Promise<void>;

private readonly parentScope: Scope;
private readonly parentScope: ScopeEntry;
private readonly agentOwner: EntryOwnerRef;

// Agent's own registries (like an app)
Expand All @@ -81,7 +81,7 @@ export class AgentScope {
private agentFlows!: FlowRegistry;

constructor(
parentScope: Scope,
parentScope: ScopeEntry,
agentId: string,
private readonly metadata: AgentMetadata,
agentToken: Token,
Expand Down Expand Up @@ -260,6 +260,14 @@ export class AgentScope {
return this.parentScope.toolUI;
}

get skills() {
return this.parentScope.skills;
}

get scopeMetadata() {
return this.parentScope.metadata;
}

// ============================================================================
// Flow Execution
// ============================================================================
Expand Down Expand Up @@ -345,6 +353,10 @@ class AgentScopeEntry {
return this.agentScope.agents;
}

get skills() {
return this.agentScope.skills;
}

get notifications() {
return this.agentScope.notifications;
}
Expand All @@ -353,6 +365,30 @@ class AgentScopeEntry {
return this.agentScope.toolUI;
}

get transportService(): undefined {
return undefined;
}

get rateLimitManager(): undefined {
return undefined;
}

get elicitationStore(): undefined {
return undefined;
}

get metadata() {
return this.agentScope.scopeMetadata;
}

get record(): undefined {
return undefined;
}

get ready() {
return this.agentScope.ready;
}

registryFlows(...flows: FlowType[]): Promise<void> {
return this.agentScope.registryFlows(...flows);
}
Expand All @@ -364,4 +400,12 @@ class AgentScopeEntry {
): Promise<FlowOutputOf<Name> | undefined> {
return this.agentScope.runFlow(name, input, deps);
}

runFlowForOutput<Name extends FlowName>(
name: Name,
input: FlowInputOf<Name>,
deps?: Map<Token, Type>,
): Promise<FlowOutputOf<Name>> {
return this.agentScope.runFlowForOutput(name, input, deps);
}
}
16 changes: 6 additions & 10 deletions libs/sdk/src/agent/flows/call-agent.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
AgentExecutionError,
RateLimitError,
} from '../../errors';
import { Scope } from '../../scope';
import { ExecutionTimeoutError, ConcurrencyLimitError, withTimeout, type SemaphoreTicket } from '@frontmcp/guard';

// ============================================================================
Expand Down Expand Up @@ -134,14 +133,12 @@ export default class CallAgentFlow extends FlowBase<typeof name> {

// Find the agent early to get its owner ID for hook filtering
const { name: toolName } = params;
const scope = this.scope as Scope;

// Agent ID is the tool name (agents use standard tool names)
const agentId = toolName;

let agent: AgentEntry | undefined;
if (scope.agents) {
agent = scope.agents.findById(agentId) ?? scope.agents.findByName(agentId);
if (this.scope.agents) {
agent = this.scope.agents.findById(agentId) ?? this.scope.agents.findByName(agentId);
}

// Store agent owner ID in state for hook filtering
Expand Down Expand Up @@ -170,8 +167,7 @@ export default class CallAgentFlow extends FlowBase<typeof name> {
async findAgent() {
this.logger.verbose('findAgent:start');

const scope = this.scope as Scope;
const agents = scope.agents;
const agents = this.scope.agents;

if (!agents) {
this.logger.warn('findAgent: no agent registry available');
Expand Down Expand Up @@ -317,7 +313,7 @@ export default class CallAgentFlow extends FlowBase<typeof name> {
async acquireQuota() {
this.logger.verbose('acquireQuota:start');

const manager = (this.scope as Scope).rateLimitManager;
const manager = this.scope.rateLimitManager;
if (!manager) {
this.state.agentContext?.mark('acquireQuota');
this.logger.verbose('acquireQuota:done (no rate limit manager)');
Expand Down Expand Up @@ -357,7 +353,7 @@ export default class CallAgentFlow extends FlowBase<typeof name> {
async acquireSemaphore() {
this.logger.verbose('acquireSemaphore:start');

const manager = (this.scope as Scope).rateLimitManager;
const manager = this.scope.rateLimitManager;
if (!manager) {
this.state.agentContext?.mark('acquireSemaphore');
this.logger.verbose('acquireSemaphore:done (no rate limit manager)');
Expand Down Expand Up @@ -434,7 +430,7 @@ export default class CallAgentFlow extends FlowBase<typeof name> {
const timeoutMs =
agent.metadata.timeout?.executeMs ??
agent.metadata.execution?.timeout ??
(this.scope as Scope).rateLimitManager?.config?.defaultTimeout?.executeMs;
this.scope.rateLimitManager?.config?.defaultTimeout?.executeMs;

try {
const doExecute = async () => {
Expand Down
9 changes: 3 additions & 6 deletions libs/sdk/src/app/instances/app.esm.instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import {
AppEntry,
AppRecord,
PluginRegistryInterface,
PromptRegistryInterface,
ProviderRegistryInterface,
RemoteAppMetadata,
ResourceRegistryInterface,
ToolRegistryInterface,
EntryOwnerRef,
PluginEntry,
AdapterEntry,
Expand Down Expand Up @@ -264,15 +261,15 @@ export class AppEsmInstance extends AppEntry<RemoteAppMetadata> {
return this._plugins;
}

override get tools(): ToolRegistryInterface {
override get tools(): ToolRegistry {
return this._tools;
}

override get resources(): ResourceRegistryInterface {
override get resources(): ResourceRegistry {
return this._resources;
}

override get prompts(): PromptRegistryInterface {
override get prompts(): PromptRegistry {
return this._prompts;
}

Expand Down
6 changes: 3 additions & 3 deletions libs/sdk/src/app/instances/app.local.instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ export class AppLocalInstance extends AppEntry<LocalAppMetadata> {
return this.appPlugins;
}

get tools(): Readonly<ToolRegistry> {
get tools(): ToolRegistry {
return this.appTools;
}

get resources(): Readonly<ResourceRegistry> {
get resources(): ResourceRegistry {
return this.appResources;
}

get prompts(): Readonly<PromptRegistry> {
get prompts(): PromptRegistry {
return this.appPrompts;
}

Expand Down
9 changes: 3 additions & 6 deletions libs/sdk/src/app/instances/app.remote.instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import {
AppEntry,
AppRecord,
PluginRegistryInterface,
PromptRegistryInterface,
ProviderRegistryInterface,
RemoteAppMetadata,
RemoteAuthConfig,
ResourceRegistryInterface,
ToolRegistryInterface,
EntryOwnerRef,
PluginEntry,
AdapterEntry,
Expand Down Expand Up @@ -296,15 +293,15 @@ export class AppRemoteInstance extends AppEntry<RemoteAppMetadata> {
return this._plugins;
}

override get tools(): ToolRegistryInterface {
override get tools(): ToolRegistry {
return this._tools;
}

override get resources(): ResourceRegistryInterface {
override get resources(): ResourceRegistry {
return this._resources;
}

override get prompts(): PromptRegistryInterface {
override get prompts(): PromptRegistry {
return this._prompts;
}

Expand Down
6 changes: 1 addition & 5 deletions libs/sdk/src/auth/auth.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
FrontMcpLogger,
AuthProviderType,
AuthProviderEntry,
AuthRegistryInterface,
AuthProviderRecord,
AuthProviderKind,
EntryOwnerRef,
Expand Down Expand Up @@ -39,10 +38,7 @@ const DEFAULT_AUTH_OPTIONS: AuthOptionsInput = {
mode: 'public',
};

export class AuthRegistry
extends RegistryAbstract<AuthProviderEntry, AuthProviderRecord, AuthProviderType[]>
implements AuthRegistryInterface
{
export class AuthRegistry extends RegistryAbstract<AuthProviderEntry, AuthProviderRecord, AuthProviderType[]> {
private readonly primary?: FrontMcpAuth;
private readonly parsedOptions: AuthOptions;
private readonly logger: FrontMcpLogger;
Expand Down
3 changes: 1 addition & 2 deletions libs/sdk/src/auth/instances/instance.remote-primary-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import WellKnownPrmFlow from '../flows/well-known.prm.flow';
import WellKnownAsFlow from '../flows/well-known.oauth-authorization-server.flow';
import WellKnownJwksFlow from '../flows/well-known.jwks.flow';
import SessionVerifyFlow from '../flows/session.verify.flow';
import { Scope } from '../../scope';

export class RemotePrimaryAuth extends FrontMcpAuth<TransparentAuthOptions> {
override ready: Promise<void>;
Expand Down Expand Up @@ -49,7 +48,7 @@ export class RemotePrimaryAuth extends FrontMcpAuth<TransparentAuthOptions> {
return Promise.resolve();
}

private async registerAuthFlows(scope: Scope) {
private async registerAuthFlows(scope: ScopeEntry) {
await scope.registryFlows(
WellKnownPrmFlow /** /.well-known/oauth-protected-resource */,
WellKnownAsFlow /** /.well-known/oauth-authorization-server */,
Expand Down
21 changes: 8 additions & 13 deletions libs/sdk/src/common/entries/app.entry.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { BaseEntry } from './base.entry';
import { AppRecord } from '../records';
import {
AdapterRegistryInterface,
AppInterface,
PluginRegistryInterface,
PromptRegistryInterface,
ProviderRegistryInterface,
ResourceRegistryInterface,
ToolRegistryInterface,
} from '../interfaces';
import { AdapterRegistryInterface, PluginRegistryInterface, ProviderRegistryInterface } from '../interfaces';
import type { SkillRegistryInterface } from '../../skill/skill.registry';
import { AppMetadata } from '../metadata';
import type ToolRegistry from '../../tool/tool.registry';
import type ResourceRegistry from '../../resource/resource.registry';
import type PromptRegistry from '../../prompt/prompt.registry';

export abstract class AppEntry<Metadata = AppMetadata> extends BaseEntry<AppRecord, AppInterface, Metadata> {
export abstract class AppEntry<Metadata = AppMetadata> extends BaseEntry<AppRecord, unknown, Metadata> {
readonly id: string;

/**
Expand All @@ -31,11 +26,11 @@ export abstract class AppEntry<Metadata = AppMetadata> extends BaseEntry<AppReco

abstract get plugins(): PluginRegistryInterface;

abstract get tools(): ToolRegistryInterface;
abstract get tools(): ToolRegistry;

abstract get resources(): ResourceRegistryInterface;
abstract get resources(): ResourceRegistry;

abstract get prompts(): PromptRegistryInterface;
abstract get prompts(): PromptRegistry;

abstract get skills(): SkillRegistryInterface;
}
Loading
Loading