Skip to content

Conversation

@frontegg-david
Copy link
Contributor

@frontegg-david frontegg-david commented Jan 27, 2026

Summary by CodeRabbit

  • New Features

    • Programmatic DirectClient API and connect utilities with platform-aware tool/result formatting and LLM-specific connectors
    • Skills MCP APIs: search, load, and list with pagination, warnings, and formatted skill payloads
    • Session/auth introspection tool added to Notes app
  • Documentation

    • New Runtime Modes and DirectClient/DirectClient Elicitation guides; updated READMEs and quickstart pointers
  • Tests

    • Extensive unit and end-to-end test coverage for connect utilities, DirectClient, platform formatting, and skills handlers

✏️ Tip: You can customize this high-level summary in your review settings.

@frontegg-david frontegg-david changed the title In memory FrontMcp server feat: In memory FrontMcp server Jan 27, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

Adds a DirectClient implementation and connect utilities with LLM-platform-aware tool/result formatting, scoped connect helpers (OpenAI/Claude/LangChain/Vercel), new skills MCP handlers and types, an auth-introspection demo tool, extensive unit and e2e tests, and comprehensive docs/READMEs.

Changes

Cohort / File(s) Summary
DirectClient types & API
libs/sdk/src/direct/client.types.ts, libs/sdk/src/direct/index.ts, libs/sdk/src/index.ts
New public types and interfaces for DirectClient, LLMPlatform, skills, elicitation, completion, client/session/connect options, and re-exports into SDK surface.
Connect utilities
libs/sdk/src/direct/connect.ts
New scoped connect(config, options) plus helpers connectOpenAI/Claude/LangChain/VercelAI and clearScopeCache(); scope caching and dynamic FrontMcpInstance resolution.
DirectClient implementation
libs/sdk/src/direct/direct-client.ts, libs/sdk/src/direct/direct-client.ts*
New DirectClientImpl with static create: in-memory transport setup, handshake, notification & elicitation wiring, tooling/resource/prompt/skills APIs, completion, subscriptions, logging, and lifecycle (close).
LLM platform formatting
libs/sdk/src/direct/llm-platform.ts
Platform detection (detectPlatform), PLATFORM_CLIENT_INFO presets, formatToolsForPlatform and formatResultForPlatform with OpenAI/Claude/LangChain/VercelAI/raw formats and helpers (sanitize schema, extract content).
Unit tests: Direct & Platform
libs/sdk/src/direct/__tests__/*.spec.ts (connect.spec.ts, direct-client.spec.ts, llm-platform.spec.ts)
Extensive unit tests covering connect behavior, DirectClientImpl flows, platform detection/formatting, session/auth propagation, tools, resources, prompts, skills, elicitation, completion, subscriptions, and logging.
E2E tests (demo app)
apps/e2e/demo-e2e-direct/e2e/connect-utilities.e2e.test.ts
New comprehensive e2e suite exercising multi-session behavior, auth token propagation, capabilities lifecycle, concurrent clients, platform connect helpers, error cases, and integrated workflows.
Demo app: auth introspection tool
apps/e2e/demo-e2e-direct/src/apps/notes/tools/get-auth-info.tool.ts, apps/e2e/demo-e2e-direct/src/apps/notes/index.ts
New GetAuthInfoTool exposing sessionId, token presence/value, and basic user claims; added to Notes app tools for tests.
Skills MCP types & handlers
libs/sdk/src/transport/mcp-handlers/skills-mcp.types.ts, libs/sdk/src/transport/mcp-handlers/skills-search-request.handler.ts, libs/sdk/src/transport/mcp-handlers/skills-load-request.handler.ts, libs/sdk/src/transport/mcp-handlers/skills-list-request.handler.ts, libs/sdk/src/transport/mcp-handlers/index.ts
New zod schemas and MCP handlers for skills/search, skills/load, skills/list; handlers validate requests, use scope.skills, transform registry results, and validate responses.
Skills handlers tests
libs/sdk/src/transport/mcp-handlers/__tests__/skills-handlers.spec.ts
Tests for skills search/load/list behavior, pagination, missing-tools handling, and registry-absence error cases.
Docs & READMEs
docs/draft/docs/deployment/direct-client.mdx, docs/draft/docs/deployment/runtime-modes.mdx, docs/draft/docs.json, docs/draft/docs/getting-started/quickstart.mdx, docs/draft/docs/servers/server.mdx, libs/sdk/src/direct/README.md, libs/sdk/src/elicitation/README.md, libs/sdk/src/skill/README.md, libs/sdk/src/direct/README.md, libs/sdk/src/direct/__tests__/*
Extensive documentation additions and READMEs describing DirectClient usage, runtime modes, elicitation, platform formatting, examples, quickstarts, and API surface; associated tests added.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Direct as DirectClientImpl
    participant Transport as In-Memory Transport
    participant MCP as MCP Server

    App->>Direct: connect(config, options)
    Direct->>Direct: getScope(config)
    Direct->>Transport: createTransport(sessionId, authToken)
    Transport->>MCP: open connection / init session
    MCP->>MCP: handshake -> version & capabilities
    MCP-->>Transport: ServerCapabilities
    Transport-->>Direct: MCP client ready
    Direct-->>App: DirectClient instance

    App->>Direct: callTool(name, args)
    Direct->>MCP: callTool request (session context)
    MCP->>MCP: execute tool
    MCP-->>Direct: CallToolResult
    Direct->>Direct: formatResultForPlatform(result, platform)
    Direct-->>App: formatted result
Loading
sequenceDiagram
    participant MCPExec as MCP Tool Execution
    participant Formatter as Platform Formatter
    participant LLM as LLM Client

    MCPExec->>Formatter: formatResultForPlatform(result, 'openai')
    alt OpenAI
        Formatter->>Formatter: extract text / parse JSON / concat
        Formatter-->>LLM: string or parsed JSON
    else Claude
        Formatter->>Formatter: preserve content array and media
        Formatter-->>LLM: content array
    else VercelAI
        Formatter->>Formatter: parse JSON from text, structure text+images
        Formatter-->>LLM: structured object
    else Raw
        Formatter-->>LLM: passthrough full CallToolResult
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped into code with a curious grin,
Direct connections stitched neatly in,
Sessions, skills, and platform rhyme,
Tests and docs snug—one happy time,
The rabbit danced and pressed "connect" on a whim!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing programmatic SDK-mode MCP access via DirectClient with platform-aware formatting, which is the primary focus of this extensive PR.
Docstring Coverage ✅ Passed Docstring coverage is 94.44% which is sufficient. The required threshold is 65.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@docs/draft/docs/deployment/direct-client.mdx`:
- Around line 260-273: The example declares `const result` twice causing a
TypeScript syntax error; update the second usage of `result` in the second
client.complete call (the one with ref: { type: 'ref/resource' }) to a different
variable name (e.g., `resultResource`) or split the examples into separate code
blocks so each has its own scope, ensuring references to `client.complete` and
any subsequent uses refer to the renamed variable.

In `@libs/sdk/src/direct/__tests__/connect.spec.ts`:
- Around line 201-216: The test is using the wrong capabilities shape; update
the call to connect in the spec so it passes a capabilities object matching
ClientCapabilities (use roots not tools). In the test around the connect
invocation (the it block that imports connect and Client), change the passed
capabilities from { tools: { listChanged: true } } to { roots: { listChanged:
true } } so the expect(Client).toHaveBeenCalledWith(...,
expect.objectContaining({ capabilities: { roots: { listChanged: true } } }))
matches the defined interface.

In `@libs/sdk/src/direct/connect.ts`:
- Around line 33-49: The cached scopePromise must not remain rejected: update
the block that sets scopePromise (using scopeCache, cacheKey, and the async
initializer that calls FrontMcpInstance.createForGraph and returns Scope) to
remove the cache entry if the async initializer throws, and rethrow an
MCP-specific error (use the project MCP error class and set the appropriate
error code) instead of a generic Error; specifically catch errors from
createForGraph/getScopes, call scopeCache.delete(cacheKey) in the catch, wrap or
convert the original error into the MCP error type with the right code, and then
throw that MCP error so subsequent connect() calls can retry.

In `@libs/sdk/src/direct/direct-client.ts`:
- Around line 135-140: Replace the generic throws for missing
serverInfo/serverCapabilities with the SDK's MCP error class (e.g., MCPError)
and include the appropriate MCP error code/enum (e.g.,
MCPErrorCode.HandshakeFailed or the project's equivalent) and context.
Specifically, in the handshake handling block where serverInfo and
serverCapabilities are checked (the code referencing serverInfo and
serverCapabilities in direct-client.ts), throw the MCP-specific error class with
a descriptive message and the correct error code rather than new Error(...).
Ensure you import the MCP error class/enum and propagate the same error shape
used across the SDK.
- Around line 212-225: The methods listTools and callTool currently return
unknown; update their signatures to return platform-specific types (e.g.,
FormattedTools for listTools and FormattedToolResult for callTool) and import
those types from client.types.ts, then ensure
formatToolsForPlatform(result.tools, this.platform) and
formatResultForPlatform(result, this.platform) are typed to produce those return
types (adjust their typings if needed) so the SDK returns explicit typed outputs
instead of unknown.
- Around line 162-185: The setupNotificationHandlers block incorrectly checks
mcpClient.onnotification (which doesn't exist) so handlers never install;
replace this pattern by calling the MCP SDK's setNotificationHandler on
mcpClient for the relevant notification schemas: register a handler for the
resources update notification that extracts uri and invokes
this.resourceUpdateHandlers.forEach(h => h(uri)), and register a handler for the
elicitation/request notification that casts params to ElicitationRequest and
calls this.handleElicitationRequest(params); ensure you use
mcpClient.setNotificationHandler(schema, handler) with the correct schema
identifiers and remove the unreachable onnotification code path.

In `@libs/sdk/src/transport/mcp-handlers/skills-list-request.handler.ts`:
- Around line 59-62: Replace the generic throw new Error in the
skills-list-request handler with the project-specific MCP error class (e.g.
MCPError): import the MCP error class used across other handlers, then throw a
new MCPError when scope.skills is missing (use the same message "Skills
capability not available"), include an MCP-specific error code (e.g.
'MissingCapability' or the project's standard code) and any relevant metadata
(handler name or scope info) so the error is consistent with other handlers like
SkillsListRequestHandler and can be recognized by MCP error handling.

In `@libs/sdk/src/transport/mcp-handlers/skills-load-request.handler.ts`:
- Around line 92-94: Replace the generic throw in the skills-load handler with
the MCP-specific error class: where the code checks "if (!skillRegistry) { throw
new Error('Skills capability not available'); }", import and throw the MCP error
(e.g., McpError) with the appropriate MCP error code/enum (e.g.,
McpErrorCode.CAPABILITY_NOT_AVAILABLE or similar) and a clear message; update
the imports at the top of skills-load-request.handler.ts to include the MCP
error class and code/enum and use them in place of the generic Error so MCP
handlers can handle the failure consistently.

In `@libs/sdk/src/transport/mcp-handlers/skills-search-request.handler.ts`:
- Around line 59-62: Replace the generic throw new Error in
skills-search-request.handler.ts when scope.skills is missing with the project’s
MCP error class (e.g., McpError/MCPError) used by other handlers: import the MCP
error class at the top of the file, and throw a new instance (for example new
McpError('CapabilityNotAvailable', 'Skills capability not available')) instead
of throw new Error('Skills capability not available') so the handler uses the
standardized MCP error type and code.
🧹 Nitpick comments (9)
libs/sdk/src/transport/mcp-handlers/skills-load-request.handler.ts (1)

129-137: Move tool lookup map construction outside the skill loop.

The toolsByName map is rebuilt for every skill in skillIds, but toolRegistry.getTools() returns the same result each time. Moving this outside the loop avoids redundant work.

Proposed fix
+    // Build a lookup map from tool registry if available (once, outside loop)
+    const toolsByName = new Map<string, { getInputJsonSchema: () => unknown }>();
+    if (toolRegistry && format !== 'instructions-only') {
+      const allTools = toolRegistry.getTools(false);
+      for (const tool of allTools) {
+        toolsByName.set(tool.name, tool);
+      }
+    }
+
     for (const skillId of skillIds) {
       // ... existing loadResult logic ...

-      // Get tool schemas if full format is requested
-      // Build a lookup map from tool registry if available
-      const toolsByName = new Map<string, { getInputJsonSchema: () => unknown }>();
-      if (toolRegistry && format !== 'instructions-only') {
-        const allTools = toolRegistry.getTools(false);
-        for (const tool of allTools) {
-          toolsByName.set(tool.name, tool);
-        }
-      }

       const toolsWithSchemas = skill.tools.map((t) => {
libs/sdk/src/transport/mcp-handlers/__tests__/skills-handlers.spec.ts (1)

161-192: Consider adding test for format: 'instructions-only' option.

The skillsLoadRequestHandler supports a format parameter that affects whether tool schemas are included. Adding a test case to verify the behavior difference between 'full' and 'instructions-only' formats would improve coverage.

Suggested additional test case
it('should not include tool schemas when format is instructions-only', async () => {
  mockSkillRegistry.loadSkill.mockResolvedValueOnce({
    skill: {
      id: 'skill-1',
      name: 'Test Skill',
      description: 'A test skill',
      instructions: 'Do the thing',
      tools: [{ name: 'tool1', purpose: 'For doing' }],
    },
    availableTools: ['tool1'],
    missingTools: [],
    isComplete: true,
    warning: undefined,
  });

  const handler = skillsLoadRequestHandler(createHandlerOptions());
  const request = {
    method: 'skills/load' as const,
    params: { skillIds: ['skill-1'], format: 'instructions-only' as const },
  };
  const ctx = createContext();

  const result = await handler.handler(request, ctx as any);

  expect(result.skills[0].tools[0].inputSchema).toBeUndefined();
});
libs/sdk/src/direct/client.types.ts (1)

38-47: Consider adding validation bounds documentation for limit.

The comment states "Maximum number of results (1-50, default: 10)" but this is purely documentation. Consider whether runtime validation of these bounds is handled elsewhere in the implementation.

apps/e2e/demo-e2e-direct/src/apps/notes/tools/get-auth-info.tool.ts (1)

43-55: Consider adding type safety for authInfo.user access.

The as any cast works for E2E testing but could mask type mismatches. If the authInfo structure from DirectClient is stable, consider defining a type interface or using a type guard.

💡 Suggested improvement
+interface DirectAuthInfo {
+  token?: string;
+  user?: {
+    sub?: string;
+    name?: string;
+    email?: string;
+    [key: string]: unknown;
+  };
+}
+
 const authInfo = ctx.authInfo;
-// User info is stored directly in authInfo (from DirectClient), not in extra
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-const user = (authInfo as any)?.user as Record<string, unknown> | undefined;
+const user = (authInfo as DirectAuthInfo | undefined)?.user;
libs/sdk/src/direct/__tests__/direct-client.spec.ts (1)

581-641: Consider adding a test for elicitation handler invocation.

The current tests verify that onElicitation returns an unsubscribe function and that submitElicitationResult calls the MCP client correctly. However, there's no test verifying that a registered handler is actually invoked when an elicitation notification arrives.

libs/sdk/src/direct/llm-platform.ts (2)

204-229: Return type doesn't reflect JSON parsing behavior.

The function signature says string but line 225 returns JSON.parse(combined) which could be any type. This works because the return type is later handled as unknown in formatResultForPlatform, but the local type annotation is misleading.

💡 Suggested fix
-function extractTextContent(result: CallToolResult): string {
+function extractTextContent(result: CallToolResult): unknown {

112-138: Add sanitization for array item schemas to ensure complete OpenAI strict mode compliance.

OpenAI's strict mode requires additionalProperties: false on every object schema node, including objects nested in array items. The function currently handles nested objects in properties but skips the items field, which means array parameters with object items would fail OpenAI validation.

Suggested enhancement
 function sanitizeSchemaForOpenAI(schema: Record<string, unknown>): Record<string, unknown> {
   const result = { ...schema };

   if (result['type'] === 'object' && !('additionalProperties' in result)) {
     result['additionalProperties'] = false;
   }

+  // Handle array items
+  if (result['items'] && typeof result['items'] === 'object') {
+    result['items'] = sanitizeSchemaForOpenAI(result['items'] as Record<string, unknown>);
+  }
+
   // Recursively handle nested objects in properties
   if (result['properties'] && typeof result['properties'] === 'object') {
     // ... existing code
   }

   return result;
 }
libs/sdk/src/direct/connect.ts (1)

19-21: Make clearScopeCache() actually reset the cache for tests.

Right now it’s a no-op; tests that call it won’t actually clear cached scopes. Reassigning the WeakMap makes the helper match its name and intent.

♻️ Suggested change
-const scopeCache = new WeakMap<object, Promise<Scope>>();
+let scopeCache = new WeakMap<object, Promise<Scope>>();
@@
 export function clearScopeCache(): void {
-  // WeakMap doesn't have a clear() method, but since it's weak-referenced,
-  // entries will be GC'd when config objects are no longer referenced.
-  // For testing, callers should ensure they don't reuse config objects.
+  scopeCache = new WeakMap<object, Promise<Scope>>();
 }

Also applies to: 269-273

libs/sdk/src/direct/direct-client.ts (1)

286-289: Ensure server cleanup even if mcpClient.close() throws.

A try/finally ensures the in-memory server is always closed.

♻️ Suggested change
   async close(): Promise<void> {
-    await this.mcpClient.close();
-    await this.closeServer?.();
+    try {
+      await this.mcpClient.close();
+    } finally {
+      await this.closeServer?.();
+    }
   }

@frontegg-david frontegg-david changed the title feat: In memory FrontMcp server feat: Implement programmatic SDK-mode MCP access via DirectClient and platform-aware formatting Jan 27, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@libs/sdk/src/direct/__tests__/direct-client.spec.ts`:
- Around line 250-260: The test uses an incorrect capabilities shape; update the
object passed to DirectClientImpl.create and the MockClient expectation to match
MCP SDK ClientCapabilities (e.g., move listChanged under roots). Replace the
capabilities arg currently `{ capabilities: { tools: { listChanged: true } } }`
with a valid structure such as `{ capabilities: { roots: { listChanged: true } }
}` and assert MockClient was called with that same structure (references:
DirectClientImpl.create and MockClient).
🧹 Nitpick comments (8)
libs/sdk/src/direct/llm-platform.ts (1)

136-167: Consider handling additional JSON Schema composition keywords.

The sanitizeSchemaForOpenAI function recursively processes properties and items, but JSON Schemas may also contain nested schemas under allOf, oneOf, anyOf, or $defs. If tools use these patterns, the additionalProperties: false constraint won't be applied to nested object schemas within them.

This is a minor gap—most simple tool schemas won't use these keywords, but it's worth noting for future robustness.

♻️ Optional enhancement to handle composition keywords
 function sanitizeSchemaForOpenAI(schema: Record<string, unknown>): Record<string, unknown> {
   const result = { ...schema };

   if (result['type'] === 'object' && !('additionalProperties' in result)) {
     result['additionalProperties'] = false;
   }

   if (result['properties'] && typeof result['properties'] === 'object') {
     const properties = result['properties'] as Record<string, Record<string, unknown>>;
     const sanitizedProperties: Record<string, Record<string, unknown>> = {};
     for (const [key, value] of Object.entries(properties)) {
       if (value && typeof value === 'object') {
         sanitizedProperties[key] = sanitizeSchemaForOpenAI(value);
       } else {
         sanitizedProperties[key] = value;
       }
     }
     result['properties'] = sanitizedProperties;
   }

   if (result['items'] && typeof result['items'] === 'object') {
     result['items'] = sanitizeSchemaForOpenAI(result['items'] as Record<string, unknown>);
   }

+  // Handle composition keywords
+  for (const keyword of ['allOf', 'oneOf', 'anyOf'] as const) {
+    if (Array.isArray(result[keyword])) {
+      result[keyword] = (result[keyword] as Record<string, unknown>[]).map((s) =>
+        s && typeof s === 'object' ? sanitizeSchemaForOpenAI(s) : s
+      );
+    }
+  }

   return result;
 }
libs/sdk/src/direct/direct-client.ts (1)

304-341: Consider documenting the server-side validation pattern.

The {} as any casts for skills operations bypass client-side schema validation. While the inline comment notes "Schema validation happens server-side," consider adding a brief JSDoc note on these methods to clarify this design choice for future maintainers.

📝 Optional: Add JSDoc clarification
+  /**
+   * Search for skills matching a query.
+   *
+   * Note: Schema validation is performed server-side; the client sends raw params.
+   */
   async searchSkills(query: string, options?: SearchSkillsOptions): Promise<SearchSkillsResult> {
libs/sdk/src/transport/mcp-handlers/skills-load-request.handler.ts (2)

77-100: Consider extracting inline type to improve readability.

The inline type definition for toolInfo is verbose. Consider defining a separate interface or type alias at the module level for clarity and reusability.

♻️ Suggested refactor
+interface ToolInfoEntry {
+  name: string;
+  purpose?: string;
+  available: boolean;
+  inputSchema?: unknown;
+  outputSchema?: unknown;
+}
+
 // Build tool info with schemas
 const toolsWithSchemas = skill.tools.map((t) => {
   const available = availableTools.includes(t.name);
-  const toolInfo: {
-    name: string;
-    purpose?: string;
-    available: boolean;
-    inputSchema?: unknown;
-    outputSchema?: unknown;
-  } = {
+  const toolInfo: ToolInfoEntry = {
     name: t.name,
     purpose: t.purpose,
     available,
   };

118-119: Session activation placeholder noted.

The comment indicates session activation is not yet implemented. Consider tracking this as a follow-up task if it's expected functionality.

Would you like me to open an issue to track the implementation of session activation in DirectClient?

libs/sdk/src/direct/__tests__/connect.spec.ts (1)

380-422: Consider adding error case tests for platform formatting.

The platform formatting tests verify the happy path but don't test behavior when listTools returns an empty array (already covered) or when tool formatting fails. Consider adding tests for edge cases like malformed tool schemas.

libs/sdk/src/direct/__tests__/direct-client.spec.ts (2)

684-686: Avoid hardcoded delays in tests.

Using setTimeout(resolve, 10) for async handler completion can cause flaky tests. Consider using jest.useFakeTimers() with jest.advanceTimersByTime(), or await a specific condition/promise instead.

♻️ Suggested approach
-      // Wait for async handler to complete
-      await new Promise((resolve) => setTimeout(resolve, 10));
+      // Wait for async handler to complete using fake timers
+      await jest.runAllTimersAsync();

Or alternatively, refactor to await the actual async operation completing by capturing and awaiting the promise from the notification handler.


604-612: Improve unsubscribe verification.

The test comment says "tested indirectly" but doesn't actually verify the handler is cleared. Consider triggering a notification after unsubscribe and verifying the handler is not called.

libs/sdk/src/transport/mcp-handlers/skills-mcp.types.ts (1)

43-45: Index signature pattern for extensibility.

The & { [key: string]: unknown } pattern allows additional fields in responses, which is useful for forward compatibility. However, this weakens type safety for consumers. Ensure this is intentional and document the extensibility purpose.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@libs/sdk/src/direct/direct-client.ts`:
- Around line 96-157: The create method currently opens the in-memory server via
createInMemoryServer and then calls mcpClient.connect and handshake methods
without ensuring close() runs on error; update DirectClient.create to wrap the
connect + handshake (the await mcpClient.connect(...) and subsequent
getServerVersion/getServerCapabilities checks) in a try/catch that calls the
close() function if any error or missing server info occurs, rethrowing the
original error (or the PublicMcpError) after cleanup so the in-memory server is
not leaked.

In `@libs/sdk/src/direct/llm-platform.ts`:
- Around line 132-182: sanitizeSchemaForOpenAI currently only adds
additionalProperties: false when the object schema lacks that key, but it must
enforce additionalProperties: false for every object schema (including when
additionalProperties is explicitly true). Update sanitizeSchemaForOpenAI so that
whenever result['type'] === 'object' you set result['additionalProperties'] =
false unconditionally (instead of checking with in), and ensure this enforcement
occurs for nested object schemas processed in properties, items, and composition
branches by letting recursive calls return schemas with additionalProperties
forced to false; reference the sanitizeSchemaForOpenAI function and the
result['type'] / result['additionalProperties'] handling to locate the change.
- Around line 108-127: FormattedToolResult uses `unknown`; define a local
JSON-serializable type (e.g., JsonValue / JsonObject / JsonArray) and replace
`unknown` in the `FormattedToolResult` union with that `JsonValue` so the SDK
API is explicit about allowed return shapes. Add the `JsonValue` type
declaration near the `FormattedToolResult` type and update the union variant to
use `JsonValue` (keep existing variants like `string`, `CallToolResult`, and the
image object intact).
🧹 Nitpick comments (4)
libs/sdk/src/transport/mcp-handlers/skills-load-request.handler.ts (2)

83-86: Consider aligning formatter selection with the format parameter.

The formatter selection uses toolRegistry existence, but formatSkillForLLMWithSchemas and formatSkillForLLM may produce different output regardless of whether schemas are actually needed. For maintainability, consider making the selection logic more explicit about when schema-enriched formatting is desired.

That said, the current implementation is functionally correct since formatSkillForLLMWithSchemas receives toolRegistry directly and can handle any format internally.


147-148: Remove redundant type assertion.

SkillsLoadResultSchema.parse() returns the Zod-inferred type, making the as SkillsLoadResult cast unnecessary. Since SkillsLoadResult is defined as z.infer<typeof SkillsLoadResultSchema>, Zod's parse output should already match.

Suggested simplification
       // Validate result against schema
-      return SkillsLoadResultSchema.parse(result) as SkillsLoadResult;
+      return SkillsLoadResultSchema.parse(result);
libs/sdk/src/direct/direct-client.ts (2)

54-85: Prefer a typed MCP client instead of any.

any hides SDK API drift and violates the project TS guidelines. Consider a type-only import for the MCP Client and use that type for the field and constructor. As per coding guidelines, ...

♻️ Suggested typing update
+import type { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js';

 export class DirectClientImpl implements DirectClient {
-  // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-  private readonly mcpClient: any;
+  private readonly mcpClient: McpClient;
   ...
   private constructor(
-    // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-    mcpClient: any,
+    mcpClient: McpClient,
     sessionId: string,
     clientInfo: ClientInfo,
     serverInfo: Implementation,
     capabilities: ServerCapabilities,
   ) {

326-395: Replace {} as any schema placeholders with a typed schema.

The client.request schema parameter can use a safe placeholder like z.any() to avoid any while still delegating validation to the server. As per coding guidelines, ...

♻️ Example pattern
+import { z } from 'zod';

+const AnySchema = z.any();

   return this.mcpClient.request(
     {
       method: 'skills/search',
       params: {
         query,
         ...options,
       },
     },
-    {} as any,
+    AnySchema,
   );

Apply the same pattern to skills/load, skills/list, and elicitation/result.

Also applies to: 408-419

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@libs/sdk/src/direct/direct-client.ts`:
- Around line 109-114: The authInfo['user'] object currently spreads
options?.session?.user after setting sub, which means a missing user.sub can
override the default; update the construction so the spread does not overwrite
the fallback—either spread options.session.user first and then set sub:
options.session.user?.sub ?? 'direct', or keep the spread last but explicitly
set sub to options.session.user?.sub ?? 'direct' (e.g., set sub after the
spread). Target the code that builds authInfo['user'] (the
options?.session?.user handling) and ensure sub uses the nullish-coalescing
fallback after the spread so 'direct' is preserved when user.sub is undefined.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@libs/sdk/src/direct/__tests__/direct-client.spec.ts`:
- Around line 27-35: The test mock for createInMemoryServer uses the
extensionless specifier which doesn't match the production dynamic import;
update the jest.mock specifier to '../../transport/in-memory-server.js' so it
aligns with the import used by DirectClientImpl and ensure all other test
imports/mocks of '../../transport/in-memory-server' (at the noted occurrences)
are changed to include the '.js' suffix; keep the mocked export names
(createInMemoryServer, clientTransport, setAuthInfo, close) unchanged so the
mock continues to satisfy DirectClientImpl's expectations.

@frontegg-david frontegg-david merged commit 07535c3 into main Jan 27, 2026
35 checks passed
@frontegg-david frontegg-david deleted the in-process-frontmcp branch January 27, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants