Skip to content

Template-based Prompt Extension System #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 15, 2025
Merged

Template-based Prompt Extension System #181

merged 8 commits into from
Apr 15, 2025

Conversation

butschster
Copy link
Collaborator

@butschster butschster commented Apr 13, 2025

This PR implements a template-based prompt extension system for the MCP Server, which allows prompts to extend other prompts with variable substitution. This enables creating reusable prompt templates that can be customized for different use cases.

Features

  • Template Type: Added a new prompt type template that can be used as a base for other prompts
  • Extension Capability: Prompts can now extend one or more templates
  • Variable Substitution: Support for replacing variables like {{title}} and {{description}} in templates
  • Inheritance Chain: Templates can extend other templates, creating an inheritance chain

Example

prompts:
  # Template for issues
  - id: template-issue
    description: Template for creating issues
    type: template
    messages:
      - role: user
        content: "Create a new issue with the following title and description: {{title}} {{description}}"

  # Template for bug issues, extending the base issue template
  - id: bug-issue
    description: Create a new bug issue
    type: prompt
    extend:
      - id: template-issue
        arguments:
          title: 'Bug: {{title}}'
          description: '{{description}}'
    schema:
      properties:
        title:
          description: The title of the bug
        description:
          description: The description of the bug

Backward Compatibility

  • Existing prompts continue to work without any changes
  • Default type is prompt if not specified

Additional notes

  • Included example configuration in docs/example/config/prompt-templates.ymll
  • Templates are not included in the JSON serialization of the registry, keeping the API clean

Summary by CodeRabbit

  • New Features
    • Introduced configurable issue prompt templates and enhanced prompt extension support for richer customization.
    • Added support for prompt templates with extensions, enabling reusable and composable prompt messages.
    • Implemented variable resolution and substitution across prompts and tool configurations for dynamic content.
    • Added new prompt types and improved prompt registry to distinguish between regular prompts and templates.
    • Introduced comprehensive feature tests and fixtures for prompts, tools, and file sources to ensure robustness.
  • Bug Fixes & Improvements
    • Strengthened error handling in prompt parsing and template resolution to prevent circular dependencies and parsing failures.
    • Improved JSON schema readability and validation without altering functionality.
    • Enhanced variable processing with composite processors for flexible variable replacement workflows.
  • Refactor & Chores
    • Streamlined internal configurations and processing methods for improved stability and maintainability.
    • Refactored test infrastructure with a new abstract feature test class to simplify writing and organizing feature tests.
    • Updated prompt and tool handler implementations to utilize improved variable resolution mechanisms.
    • Added array access support to document registry for safer document retrieval.

- Add support for template-based prompts that can be extended
- Implement variable substitution in templates using existing variable system
- Allow templates to extend other templates (inheritance chain)
- Add proper error handling for circular dependencies or missing templates
- Maintain backward compatibility with existing prompts

Closes #180
@butschster butschster added enhancement New feature or request mcp MCP server components labels Apr 13, 2025
@butschster butschster added this to the 1.x milestone Apr 13, 2025
@butschster butschster self-assigned this Apr 13, 2025
Copy link

coderabbitai bot commented Apr 13, 2025

Walkthrough

This pull request introduces a new YAML configuration for issue prompt templates, updates JSON schema formatting for readability, and enhances the variable replacement mechanism by introducing a composite processor along with a new interface. It also extends prompt functionality by adding support for prompt extensions, template resolution, and a new enumeration for prompt types. Additionally, error handling improvements and minor adjustments in HTTP tool request processing and null checking have been applied, along with the removal of an obsolete unit test. Several new feature test classes and YAML fixtures have been added to verify prompt, tool, and file source configurations and behaviors.

Changes

File(s) Change Summary
docs/example/config/prompt-templates.yml Added new YAML config with multiple prompt templates (base, bug, feature, complex, priority bug) for issue creation.
json-schema.json Reformatted schema definitions (arrays, required properties, enums) for improved readability without altering validation logic; added new properties for prompt extensions.
src/Application/Bootloader/VariableBootloader.php Introduced new dependencies (CompositeProcessor, VariableReplacementProcessorInterface) and registered a singleton for variable replacement processing.
src/Lib/Variable/CompositeProcessor.php, VariableReplacementProcessor.php, VariableReplacementProcessorInterface.php, VariableResolver.php, tests/src/Unit/Lib/Variable/VariableResolverTest.php Implemented an interface-based composite variable replacement system; added a with method in VariableResolver and removed a default processor test.
src/Console/GenerateCommand.php Added a null check before calling renderImports to prevent null values from being processed.
Multiple files in src/McpServer/Prompt/...
(Exception/PromptParsingException.php, Exception/TemplateResolutionException.php, Extension/PromptDefinition.php, Extension/PromptExtension.php, Extension/PromptExtensionArgument.php, Extension/PromptExtensionVariableProvider.php, Extension/TemplateResolver.php, PromptConfigFactory.php, PromptParserPlugin.php, PromptProviderInterface.php, PromptRegistry.php, PromptRegistryInterface.php, PromptType.php, PromptMessageProcessor.php)
Added prompt extension support and template resolution functionality. This includes new classes for prompt extensions and arguments, a new TemplateResolver with merging/substitution logic, enhanced prompt configuration/parsing, updated registry methods filtering prompts vs. templates, a new enum to distinguish prompt types, and a message processor for variable substitution.
src/McpServer/Action/Prompts/GetPromptAction.php Simplified prompt retrieval by removing manual message processing and using the prompt provider's argument-aware get method.
src/McpServer/Tool/Types/HttpToolHandler.php Integrated VariableResolver into HTTP request processing to handle dynamic variable substitutions.
src/Document/DocumentRegistry.php Implemented ArrayAccess interface with read-only access to documents; disallow direct set/unset via array syntax.
tests/src/Feature/FeatureTestCases.php, tests/src/Feature/Compiler/AbstractCompilerTestCase.php Introduced new abstract feature test base class; refactored existing compiler test base to use it and simplified test lifecycle and assertions.
Multiple new test classes in tests/src/Feature/McpServer/Prompt/ Added feature tests for prompt parsing, JSON serialization, registry operations, schema validation, and template prompt functionality.
Multiple new test classes in tests/src/Feature/McpServer/Tool/ Added feature tests for basic tool config, HTTP tools, run tools, and tool handler executions.
Multiple new test classes in tests/src/Feature/Source/File/ Added tests for file source filtering, serialization, basic functionality, and tree view features.
Multiple new YAML fixture files under tests/fixtures/ Added fixtures for prompts, prompt templates, tools (basic, HTTP, run, error cases), and file sources (basic, filtered, invalid, tree view, test files).
tests/src/context.yaml Added new test documentation configuration organizing test groups and sources for prompts, tools, and compiler tests.
src/Config/Import/ImportRegistry.php Enhanced generic type annotations with template parameter for imports; updated property and method PHPDoc accordingly; removed redundant PHPDoc on some methods.
src/Config/Registry/RegistryInterface.php Changed PHPDoc return type of getItems() from array<TItem> to list<TItem>.
src/McpServer/Tool/ToolProviderInterface.php Changed PHPDoc return type of all() from associative array to list of ToolDefinition.
src/McpServer/Tool/ToolRegistry.php Removed PHPDoc comment blocks above getType() and getItems() methods without changing functionality.

Sequence Diagram(s)

sequenceDiagram
    participant Plugin as PromptParserPlugin
    participant Registry as PromptRegistry
    participant Resolver as TemplateResolver
    participant Logger as LoggerInterface

    Plugin->>Registry: Retrieve registered prompts
    loop For each prompt with extensions
        Plugin->>Resolver: resolve(prompt)
        Resolver->>Resolver: mergeMessages() & substituteMessages()
        Resolver-->>Plugin: return resolved prompt
        Plugin->>Registry: register(resolved prompt)
    end
    alt Resolution Error Occurs
        Resolver-->>Plugin: throw TemplateResolutionException
        Plugin->>Logger: log warning/error
        Plugin-->>Registry: throw PromptParsingException
    end
Loading
sequenceDiagram
    participant Handler as HttpToolHandler
    participant Resolver as VariableResolver
    participant Composite as CompositeProcessor

    Handler->>Resolver: with(ToolArgumentsProvider)
    Resolver->>Composite: Process variable replacements (iterate processors)
    Composite-->>Resolver: Return resolved string
    Resolver-->>Handler: Return updated URL/headers/body
Loading

Possibly related issues

Poem

I'm a bunny, hopping through the code,
Leaping over bugs to lighten the load.
With templates and prompts in a rhythmic dance,
My whiskers twitch at each fresh chance.
In a meadow of logic and processor streams,
I celebrate new changes, chasing my dreams.
🐰🌟 Hop on, code! Hop on!

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96fe621 and 86c7dcf.

📒 Files selected for processing (6)
  • src/Config/Import/ImportRegistry.php (1 hunks)
  • src/Config/Registry/RegistryInterface.php (1 hunks)
  • src/Document/DocumentRegistry.php (3 hunks)
  • src/McpServer/Prompt/PromptRegistry.php (5 hunks)
  • src/McpServer/Tool/ToolProviderInterface.php (1 hunks)
  • src/McpServer/Tool/ToolRegistry.php (0 hunks)
💤 Files with no reviewable changes (1)
  • src/McpServer/Tool/ToolRegistry.php
✅ Files skipped from review due to trivial changes (1)
  • src/Config/Registry/RegistryInterface.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Document/DocumentRegistry.php
  • src/McpServer/Prompt/PromptRegistry.php
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/Config/Import/ImportRegistry.php (2)
src/McpServer/Prompt/PromptRegistry.php (1)
  • Singleton (16-93)
src/McpServer/Tool/ToolRegistry.php (1)
  • Singleton (16-72)
🔇 Additional comments (4)
src/Config/Import/ImportRegistry.php (3)

12-13: Great improvement to type safety with generics

The addition of the generic template parameter TImport constrained to SourceConfigInterface enhances type safety and better communicates how this registry should be used by implementing classes.


18-18: Improved type annotation precision

Changing from a general array to a list<TImport> adds clarity about the expected data structure - a sequentially indexed array rather than an associative one.


23-23: Consistent use of generic parameter

This properly updates the parameter type to use the generic TImport type parameter, maintaining consistency with the class-level template declaration.

src/McpServer/Tool/ToolProviderInterface.php (1)

26-26:

❓ Verification inconclusive

Improved return type precision

Changing from array<string, ToolDefinition> to list<ToolDefinition> clarifies that the method returns a sequentially indexed array rather than an associative array. This aligns with implementation in ToolRegistry which uses array_values() to return a numerically indexed array.


🏁 Script executed:

#!/bin/bash
# Verify that implementations of this interface correctly return a sequential array

# First check the concrete implementation
echo "Checking ToolRegistry implementation:"
rg -A 3 "public function all\(\): array" --type php src/McpServer/Tool/ToolRegistry.php

# Then check if there are any direct usages of the return value that might expect an associative array
echo "Checking for potential breaking usages:"
rg "->all\(\)" --type php -A 5 -B 5 | grep -v "getItems" | grep "\["

Length of output: 816


Return Type Update Looks Sound – Please Manually Verify Usage Patterns

  • The change from array<string, ToolDefinition> to list<ToolDefinition> in ToolProviderInterface is consistent with the implementation in ToolRegistry, which returns its items via $this->getItems() (and by design, uses array_values() elsewhere to ensure sequential indexing).
  • Our initial check of ToolRegistry::all() shows it returns an array as expected.
  • However, an automated search for usages of ->all() with the original command encountered errors. To be extra safe, please re-run a search using a fixed-string query (e.g., using rg --fixed-strings "->all()" --type=php -A 5 -B 5) or verify manually that no part of the codebase depends on associative array keys from this method.
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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

🧹 Nitpick comments (4)
src/McpServer/Prompt/PromptType.php (1)

18-28: Well-implemented enum with smart fallback design

The fromString method provides a clean way to map string values to enum cases with appropriate default handling. The case-insensitive comparison is good practice.

Consider logging or warning when an invalid type string is encountered, as silently defaulting to Prompt could potentially hide configuration errors. This could help with debugging when users mistype template types.

src/Lib/Variable/CompositeProcessor.php (1)

10-11: Consider using the interface type in PHPDoc.

While functionally correct, the PHPDoc specifies VariableReplacementProcessor[] but should use VariableReplacementProcessorInterface[] to follow the Liskov Substitution Principle and allow any implementation of the interface.

    /**
-     * @param VariableReplacementProcessor[] $processors
+     * @param VariableReplacementProcessorInterface[] $processors
     */
src/McpServer/Tool/Types/HttpToolHandler.php (1)

145-146: Incorrect comment in query parameters section.

The comment is duplicated from above, but this section is processing body parameters, not query parameters.

-        // Process query parameters
+        // Process body parameters
        if (isset($requestConfig['body']) && \is_array($requestConfig['body'])) {
docs/example/config/prompt-templates.yml (1)

59-76: Consider adding a 'priority' property to the schema.

You reference {{priority}} in the final content but do not define it in the schema. To help users understand and populate the “priority” attribute, consider adding a corresponding schema property, for example:

schema:
  properties:
    priority:
      description: The priority level of the issue
  required:
    - priority
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c3bd76 and ea042a6.

📒 Files selected for processing (23)
  • docs/example/config/prompt-templates.yml (1 hunks)
  • json-schema.json (7 hunks)
  • src/Application/Bootloader/VariableBootloader.php (2 hunks)
  • src/Console/GenerateCommand.php (1 hunks)
  • src/Lib/Variable/CompositeProcessor.php (1 hunks)
  • src/Lib/Variable/VariableReplacementProcessor.php (1 hunks)
  • src/Lib/Variable/VariableReplacementProcessorInterface.php (1 hunks)
  • src/Lib/Variable/VariableResolver.php (1 hunks)
  • src/McpServer/Prompt/Exception/PromptParsingException.php (1 hunks)
  • src/McpServer/Prompt/Exception/TemplateResolutionException.php (1 hunks)
  • src/McpServer/Prompt/Extension/PromptDefinition.php (3 hunks)
  • src/McpServer/Prompt/Extension/PromptExtension.php (1 hunks)
  • src/McpServer/Prompt/Extension/PromptExtensionArgument.php (1 hunks)
  • src/McpServer/Prompt/Extension/PromptExtensionVariableProvider.php (1 hunks)
  • src/McpServer/Prompt/Extension/TemplateResolver.php (1 hunks)
  • src/McpServer/Prompt/PromptConfigFactory.php (3 hunks)
  • src/McpServer/Prompt/PromptParserPlugin.php (3 hunks)
  • src/McpServer/Prompt/PromptProviderInterface.php (2 hunks)
  • src/McpServer/Prompt/PromptRegistry.php (3 hunks)
  • src/McpServer/Prompt/PromptRegistryInterface.php (1 hunks)
  • src/McpServer/Prompt/PromptType.php (1 hunks)
  • src/McpServer/Tool/Types/HttpToolHandler.php (4 hunks)
  • tests/src/Unit/Lib/Variable/VariableResolverTest.php (0 hunks)
💤 Files with no reviewable changes (1)
  • tests/src/Unit/Lib/Variable/VariableResolverTest.php
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/Lib/Variable/VariableReplacementProcessorInterface.php (2)
src/Lib/Variable/CompositeProcessor.php (1)
  • process (16-23)
src/Lib/Variable/VariableReplacementProcessor.php (1)
  • process (29-44)
src/McpServer/Prompt/Exception/TemplateResolutionException.php (1)
src/McpServer/Prompt/Exception/PromptParsingException.php (1)
  • PromptParsingException (10-13)
src/Lib/Variable/CompositeProcessor.php (3)
src/Lib/Variable/VariableResolver.php (1)
  • __construct (12-14)
src/Lib/Variable/VariableReplacementProcessor.php (2)
  • __construct (17-21)
  • process (29-44)
src/Lib/Variable/VariableReplacementProcessorInterface.php (1)
  • process (15-15)
src/Console/GenerateCommand.php (2)
src/Config/Registry/ConfigRegistryAccessor.php (1)
  • getImports (48-51)
src/Console/Renderer/GenerateCommandRenderer.php (1)
  • renderImports (42-68)
src/McpServer/Tool/Types/HttpToolHandler.php (2)
src/Lib/Variable/VariableResolver.php (3)
  • VariableResolver (10-39)
  • with (16-22)
  • resolve (27-38)
src/Lib/Variable/VariableReplacementProcessor.php (1)
  • VariableReplacementProcessor (15-70)
src/McpServer/Prompt/PromptRegistryInterface.php (3)
src/McpServer/Prompt/Extension/PromptDefinition.php (1)
  • PromptDefinition (11-70)
src/McpServer/Prompt/PromptRegistry.php (2)
  • has (44-47)
  • get (30-42)
src/McpServer/Prompt/PromptProviderInterface.php (2)
  • has (16-16)
  • get (24-24)
src/McpServer/Prompt/Extension/PromptExtension.php (2)
src/McpServer/Prompt/Extension/PromptExtensionArgument.php (2)
  • __construct (12-15)
  • PromptExtensionArgument (10-16)
src/McpServer/Prompt/Extension/PromptDefinition.php (1)
  • __construct (13-21)
src/McpServer/Prompt/PromptParserPlugin.php (4)
src/McpServer/Prompt/Extension/TemplateResolver.php (3)
  • LoggerPrefix (20-151)
  • __construct (23-27)
  • resolve (36-95)
src/McpServer/Prompt/Exception/TemplateResolutionException.php (1)
  • TemplateResolutionException (10-13)
src/McpServer/Prompt/PromptConfigFactory.php (1)
  • createFromConfig (27-65)
src/McpServer/Prompt/PromptRegistryInterface.php (1)
  • register (14-14)
src/McpServer/Prompt/PromptConfigFactory.php (4)
src/McpServer/Prompt/Extension/PromptDefinition.php (1)
  • PromptDefinition (11-70)
src/McpServer/Prompt/Extension/PromptExtension.php (2)
  • PromptExtension (10-49)
  • fromArray (28-48)
src/McpServer/Prompt/PromptType.php (1)
  • fromString (18-28)
src/McpServer/Prompt/Exception/PromptParsingException.php (1)
  • PromptParsingException (10-13)
src/McpServer/Prompt/PromptRegistry.php (2)
src/McpServer/Prompt/Extension/PromptDefinition.php (2)
  • PromptDefinition (11-70)
  • jsonSerialize (23-48)
src/McpServer/Prompt/PromptRegistryInterface.php (2)
  • get (29-29)
  • has (21-21)
src/Application/Bootloader/VariableBootloader.php (3)
src/Lib/Variable/CompositeProcessor.php (1)
  • CompositeProcessor (7-24)
src/Lib/Variable/Provider/CompositeVariableProvider.php (1)
  • CompositeVariableProvider (10-75)
src/Lib/Variable/VariableReplacementProcessor.php (1)
  • VariableReplacementProcessor (15-70)
src/McpServer/Prompt/Extension/PromptDefinition.php (1)
src/McpServer/Prompt/Extension/PromptExtension.php (1)
  • PromptExtension (10-49)
src/McpServer/Prompt/Extension/TemplateResolver.php (8)
src/Lib/Variable/VariableReplacementProcessor.php (1)
  • VariableReplacementProcessor (15-70)
src/Lib/Variable/VariableResolver.php (4)
  • VariableResolver (10-39)
  • __construct (12-14)
  • resolve (27-38)
  • with (16-22)
src/McpServer/Prompt/Exception/TemplateResolutionException.php (1)
  • TemplateResolutionException (10-13)
src/McpServer/Prompt/Extension/PromptExtensionArgument.php (1)
  • __construct (12-15)
src/McpServer/Prompt/Extension/PromptExtension.php (1)
  • __construct (16-19)
src/McpServer/Prompt/Extension/PromptExtensionVariableProvider.php (3)
  • __construct (22-25)
  • get (32-35)
  • PromptExtensionVariableProvider (12-53)
src/McpServer/Prompt/PromptProviderInterface.php (1)
  • get (24-24)
src/McpServer/Prompt/PromptRegistryInterface.php (1)
  • get (29-29)
src/Lib/Variable/VariableResolver.php (1)
src/Lib/Variable/CompositeProcessor.php (1)
  • CompositeProcessor (7-24)
🪛 GitHub Check: psalm (ubuntu-latest, 8.3, locked)
src/McpServer/Prompt/PromptRegistry.php

[failure] 44-44: ParamNameMismatch
src/McpServer/Prompt/PromptRegistry.php:44:32: ParamNameMismatch: Argument 1 of Butschster\ContextGenerator\McpServer\Prompt\PromptRegistry::has has wrong name $id, expecting $name as defined by Butschster\ContextGenerator\McpServer\Prompt\PromptProviderInterface::has (see https://psalm.dev/230)


[failure] 30-30: ParamNameMismatch
src/McpServer/Prompt/PromptRegistry.php:30:32: ParamNameMismatch: Argument 1 of Butschster\ContextGenerator\McpServer\Prompt\PromptRegistry::get has wrong name $id, expecting $name as defined by Butschster\ContextGenerator\McpServer\Prompt\PromptProviderInterface::get (see https://psalm.dev/230)

src/McpServer/Prompt/Extension/TemplateResolver.php

[failure] 134-134: UndefinedMagicPropertyFetch
src/McpServer/Prompt/Extension/TemplateResolver.php:134:24: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\ImageContent::$text is not defined (see https://psalm.dev/218)


[failure] 134-134: UndefinedMagicPropertyFetch
src/McpServer/Prompt/Extension/TemplateResolver.php:134:24: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\EmbeddedResource::$text is not defined (see https://psalm.dev/218)

🪛 GitHub Actions: 🔍 Static analysis
src/McpServer/Prompt/Extension/TemplateResolver.php

[error] 134-134: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\EmbeddedResource::$text is not defined (see https://psalm.dev/218)

🔇 Additional comments (54)
src/McpServer/Prompt/Exception/PromptParsingException.php (1)

10-10: Good architectural change to enable exception inheritance

Removing the final keyword is appropriate here since this class is now extended by the new TemplateResolutionException. This change enables a proper exception hierarchy for the template system while maintaining backward compatibility.

src/Lib/Variable/CompositeProcessor.php (2)

7-14: Class declaration and constructor look good.

The CompositeProcessor class implementation follows the composite design pattern, allowing for sequencing multiple processors. The final readonly modifier ensures immutability, which is a good practice for this type of utility class.


16-23: Implementation of process method is clean and effective.

The method correctly iterates through each processor and applies them sequentially, passing the transformed text to each subsequent processor in the chain.

src/McpServer/Prompt/PromptRegistryInterface.php (4)

7-8: Import added correctly for PromptDefinition.

The addition of this import supports the new method signatures that return the PromptDefinition type.


11-14: Updated documentation for register method.

Documentation has been simplified while maintaining clarity.


16-22: Well-documented has method added.

The new has method provides a clean way to check for prompt existence by ID with clear parameter documentation.


23-29: Well-documented get method added.

The new get method provides a standard way to retrieve prompt definitions with appropriate exception documentation when the prompt doesn't exist.

src/Application/Bootloader/VariableBootloader.php (2)

9-16: Imports added for new composite processor pattern.

The added imports correctly set up the dependencies needed for the new singleton definition.


56-61: Singleton definition for variable replacement processor interface.

Good implementation of the composite pattern in the DI container. This allows for extending the variable replacement functionality with multiple processors while maintaining a clean interface.

src/McpServer/Tool/Types/HttpToolHandler.php (6)

12-12: Import added for VariableResolver.

Correctly imports the VariableResolver class that replaces direct use of the VariableReplacementProcessor.


22-28: Constructor updated with VariableResolver dependency.

The constructor now injects the VariableResolver, which is a good practice for dependency injection.


117-119: Improved variable replacement with composite pattern.

Using the with method on the VariableResolver creates a new composite processor that chains the existing processor with the new one for arguments, which is more flexible than directly creating a processor.


121-123: URL variable resolution using the resolver.

The code now delegates to the VariableResolver for processing, which is cleaner and more consistent.


127-129: Headers variable resolution using the resolver.

Headers are now processed consistently using the VariableResolver.


140-142: Variable resolution for query and body parameters.

The code now uses the resolver to process variables in both query and body parameters.

Also applies to: 147-149

src/McpServer/Prompt/Extension/PromptExtension.php (1)

1-49: LGTM! Well-structured implementation of prompt extension mechanism.

The PromptExtension class is well-designed with appropriate immutability (final readonly), clear responsibility (representing template extension configuration), and robust validation in the fromArray method. The error messages are descriptive and will help users understand configuration issues.

src/Lib/Variable/VariableResolver.php (2)

13-13: Good refactoring to support interface-based dependency injection.

Changing the constructor parameter from concrete VariableReplacementProcessor to VariableReplacementProcessorInterface with a default CompositeProcessor improves flexibility while maintaining backward compatibility.


16-22: Well-implemented composition pattern for immutable objects.

The new with() method follows best practices for immutable objects by returning a new instance with combined processors rather than modifying the existing one. This approach maintains the object's integrity while allowing for flexible composition.

src/McpServer/Prompt/PromptConfigFactory.php (4)

8-9: LGTM! Added necessary imports for the new extension functionality.


45-53: Good implementation of prompt type and extension parsing.

The code correctly determines the prompt type and parses extensions from the configuration in a clean, logical manner.


62-64: LGTM! Updated constructor parameters properly.

Constructor call updated to include the new type and extensions parameters.


67-103: Well-structured extension parsing with proper error handling.

The parseExtensions method is well-implemented with:

  • Clear validation of extension configurations
  • Proper exception handling with contextual error messages
  • Good use of the existing PromptExtension::fromArray method
  • Appropriate conversion of generic exceptions to domain-specific ones with context

This implementation will provide clear, helpful error messages when extension configurations are invalid.

src/McpServer/Prompt/Extension/PromptDefinition.php (4)

5-7: LGTM! Namespace and import changes align with the new structure.

Moving the class to the Extension namespace and adding the PromptType import makes sense given the new functionality.


18-20: Good extension of constructor parameters with appropriate defaults.

Adding the prompt type and extensions parameters with sensible defaults ensures backward compatibility while enabling the new template functionality.


42-46: LGTM! Updated JSON serialization to include new properties.

The serialization now includes the prompt type and extensions, which is necessary for the template-based prompt system.


50-69: Well-implemented extension serialization method.

The serializeExtensions method:

  • Handles empty arrays appropriately
  • Correctly transforms extension objects to the configuration format
  • Preserves the structure needed for recreation via the fromArray method

This maintains a consistent format between the configuration and serialized output.

docs/example/config/prompt-templates.yml (1)

1-9: Looks great for the base template.

The base template-issue structure appears correct, and the variable placeholders {{title}} and {{description}} are clearly integrated.

src/McpServer/Prompt/PromptParserPlugin.php (2)

7-23: Constructor injection and logging prefix look fine.

The addition of TemplateResolver to the constructor and the usage of #[LoggerPrefix(prefix: 'prompt.parser')] attribute provide clarity and maintain consistency with the new template resolution approach.


69-92: Template resolution second pass is well-structured.

Resolving template extensions after the initial registry pass is a clean approach to ensure all templates are registered first. This mechanism avoids partial or circular references and aligns well with the new TemplateResolver.

src/McpServer/Prompt/PromptRegistry.php (3)

54-60: Efficient template retrieval.

allTemplates() correctly filters prompts to return only those of PromptType::Template. This aligns well with the new approach of separating regular prompts from templates.


70-83: Selective retrieval of regular prompts.

Filtering out template-type prompts in getItems() helps maintain consistent serialization and operations where only non-template prompts are needed.


87-89: Excluding templates from JSON serialization is appropriate.

Only serializing the actual prompts rather than template definitions keeps the API and configuration output concise, preventing unnecessary clutter.

src/McpServer/Prompt/Extension/PromptExtensionVariableProvider.php (4)

12-25: All variables from extension arguments are properly stored.

This constructor fully aligns with the typed property $variables declared above, ensuring type safety and clarity. No issues found.


27-31: has method is straightforward and efficient.

Using array_key_exists is appropriate here, and returning a boolean aligns with typical usage.


32-35: get method is concise and handles the fallback correctly.

Returning null when the variable is absent is a clear and safe approach.


37-52: Private method createVariablesFromArguments is readable and safe.

Populating $variables using each extension argument’s name and value is sound. No recommendations at this time.

src/McpServer/Prompt/Extension/TemplateResolver.php (2)

36-95: Extension resolution logic is coherent and respects circular dependency checks.

This section effectively prevents infinite recursion and merges template messages if present. No issues flagged here.


97-115: mergeMessages method covers different scenarios cleanly.

The distinction between using template messages when the prompt has none, and retaining prompt messages otherwise, seems intentional and correct.

json-schema.json (10)

272-278: Tool type enumeration is clearly defined.

Specifying "run" and "http" for "type" clarifies the permitted tool types and sets "run" as a default. This is well-structured.


315-317: Conditional requirement of commands or requests.

The schema now mandates commands if type is "run", and requests if type is "http", ensuring proper validation for each type of tool.

Also applies to: 329-331


338-340: New required property url and extended HTTP methods.

Marking url as required gives clarity for request generation. Enumerating "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", and "OPTIONS" covers most common methods.

Also applies to: 348-356


442-444: id is now a required property for prompt definitions.

This ensures every prompt has a unique identifier. Good addition for referencing prompts.


454-462: Added type property for prompt specification.

The schema now supports "prompt" or "template", aligning with the new template-based architecture.


488-488: Clarified placeholder usage in message content.

Referring to both ${variableName} and {{variableName}} placeholders. Matches the variable substitution logic in the code.


492-514: New extend property for template inheritance.

Allowing an array of templates with arguments fosters modular and consistent prompts. The schema definition is aligned with the new extension approach.


526-538: Conditional requirement for messages or extend when type is "prompt".

This ensures a prompt either directly defines messages or inherits them from one or more templates.


544-545: template requires messages.

When type is "template", requiring messages maintains consistency by ensuring a template has base content to be inherited.

Also applies to: 548-552


1346-1348: Refined maxFiles type definition in composerSource.

Restricting it to integer reflects its usage and clarifies expectation. No issues found.

src/McpServer/Prompt/Exception/TemplateResolutionException.php (1)

7-13: Properly structured exception handling for template resolution.

The implementation follows good exception handling practices by creating a specialized exception type for template resolution failures. The class extends PromptParsingException appropriately, aligning with the hierarchical error model.

src/McpServer/Prompt/PromptProviderInterface.php (1)

7-8: LGTM - Appropriate import for type hinting.

Adding the import for PromptDefinition ensures proper type hinting for the interface methods.

src/Console/GenerateCommand.php (1)

96-99: Improved null safety when handling imports.

This change properly checks if $imports is null before attempting to render them, preventing potential null reference exceptions. The getImports() method can return null according to its signature in ConfigRegistryAccessor.

src/Lib/Variable/VariableReplacementProcessor.php (1)

15-15: Good application of the Interface Segregation Principle.

Implementing the VariableReplacementProcessorInterface improves code modularity and allows for better dependency injection and composition. This change supports the new template-based prompt system by enabling more flexible variable replacement strategies.

src/Lib/Variable/VariableReplacementProcessorInterface.php (1)

7-16: Well-defined interface with clear purpose and good documentation.

The interface establishes a clean contract for variable replacement processors with appropriate PHPDoc comments. This abstraction will allow for different implementations (like the new CompositeProcessor mentioned in the summary) and makes the system more flexible and extensible.

src/McpServer/Prompt/Extension/PromptExtensionArgument.php (1)

7-16: Clean implementation with good use of PHP 8 features.

This class is well-designed for representing template arguments with:

  • Appropriate use of final readonly for immutability
  • Constructor property promotion for concise code
  • Clear naming and documentation explaining its purpose within the template system

The class fits well with the PR's objectives of implementing a template-based prompt extension system.

Comment on lines 116 to 151
/**
* Applies variable substitution to template messages.
*
* @param PromptMessage[] $messages The messages to process
* @param PromptExtensionArgument[] $arguments The variables to substitute
* @return PromptMessage[] The processed messages
*/
private function substituteMessages(array $messages, array $arguments): array
{
$result = [];

// Create a variable provider for the extension arguments
$variableProvider = new PromptExtensionVariableProvider($arguments);

// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));

foreach ($messages as $message) {
$content = $message->content->text;
$substitutedContent = $resolver->resolve($content);

$this->logger?->debug('Template message processed', [
'original' => $content,
'resolved' => $substitutedContent,
]);

// Create a new message with the substituted content
$result[] = new PromptMessage(
role: $message->role,
content: new TextContent(text: $substitutedContent),
);
}

return $result;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential undefined property access in $message->content->text.

Static analysis reports [UndefinedMagicPropertyFetch] on line 134, indicating that $message->content may not always have a text property if it’s not a TextContent. This could cause a runtime error.

Consider adding a type check, for example:

- $content = $message->content->text;
+ if (!$message->content instanceof TextContent) {
+     $this->logger?->warning('Expected text-based content, got different type', [
+         'content_class' => get_class($message->content),
+     ]);
+     continue;
+ }
+ $content = $message->content->text;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Applies variable substitution to template messages.
*
* @param PromptMessage[] $messages The messages to process
* @param PromptExtensionArgument[] $arguments The variables to substitute
* @return PromptMessage[] The processed messages
*/
private function substituteMessages(array $messages, array $arguments): array
{
$result = [];
// Create a variable provider for the extension arguments
$variableProvider = new PromptExtensionVariableProvider($arguments);
// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));
foreach ($messages as $message) {
$content = $message->content->text;
$substitutedContent = $resolver->resolve($content);
$this->logger?->debug('Template message processed', [
'original' => $content,
'resolved' => $substitutedContent,
]);
// Create a new message with the substituted content
$result[] = new PromptMessage(
role: $message->role,
content: new TextContent(text: $substitutedContent),
);
}
return $result;
}
}
/**
* Applies variable substitution to template messages.
*
* @param PromptMessage[] $messages The messages to process
* @param PromptExtensionArgument[] $arguments The variables to substitute
* @return PromptMessage[] The processed messages
*/
private function substituteMessages(array $messages, array $arguments): array
{
$result = [];
// Create a variable provider for the extension arguments
$variableProvider = new PromptExtensionVariableProvider($arguments);
// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));
foreach ($messages as $message) {
+ if (!$message->content instanceof TextContent) {
+ $this->logger?->warning('Expected text-based content, got different type', [
+ 'content_class' => get_class($message->content),
+ ]);
+ continue;
+ }
- $content = $message->content->text;
+ $content = $message->content->text;
$substitutedContent = $resolver->resolve($content);
$this->logger?->debug('Template message processed', [
'original' => $content,
'resolved' => $substitutedContent,
]);
// Create a new message with the substituted content
$result[] = new PromptMessage(
role: $message->role,
content: new TextContent(text: $substitutedContent),
);
}
return $result;
}
}
🧰 Tools
🪛 GitHub Check: psalm (ubuntu-latest, 8.3, locked)

[failure] 134-134: UndefinedMagicPropertyFetch
src/McpServer/Prompt/Extension/TemplateResolver.php:134:24: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\ImageContent::$text is not defined (see https://psalm.dev/218)


[failure] 134-134: UndefinedMagicPropertyFetch
src/McpServer/Prompt/Extension/TemplateResolver.php:134:24: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\EmbeddedResource::$text is not defined (see https://psalm.dev/218)

🪛 GitHub Actions: 🔍 Static analysis

[error] 134-134: UndefinedMagicPropertyFetch: Magic instance property Mcp\Types\EmbeddedResource::$text is not defined (see https://psalm.dev/218)

Comment on lines +33 to +38
/**
* Gets all non-template prompts.
*
* @return array<string, PromptDefinition>
*/
public function allTemplates(): array;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Documentation and method name appear to be inconsistent.

The method name allTemplates() suggests it returns all templates, but the documentation comment states it returns "all non-template prompts". This discrepancy could cause confusion for developers implementing this interface.

-    /**
-     * Gets all non-template prompts.
-     *
-     * @return array<string, PromptDefinition>
-     */
-    public function allTemplates(): array;
+    /**
+     * Gets all template prompts.
+     *
+     * @return array<string, PromptDefinition>
+     */
+    public function allTemplates(): array;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Gets all non-template prompts.
*
* @return array<string, PromptDefinition>
*/
public function allTemplates(): array;
/**
* Gets all template prompts.
*
* @return array<string, PromptDefinition>
*/
public function allTemplates(): array;

@butschster butschster moved this to In review in Context Generator Apr 13, 2025
Copy link

@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: 2

♻️ Duplicate comments (1)
src/McpServer/Prompt/Extension/TemplateResolver.php (1)

134-136: ⚠️ Potential issue

Replace assertion with runtime type check for production safety.

The current assertion \assert($message->content instanceof TextContent) only checks the type during development and won't provide protection in production when assertions are disabled. This issue was previously flagged in static analysis.

Replace with a proper runtime type check:

- \assert($message->content instanceof TextContent);
- $content = $message->content->text;
+ if (!$message->content instanceof TextContent) {
+     $this->logger?->warning('Expected text-based content, got different type', [
+         'content_class' => get_class($message->content),
+     ]);
+     continue;
+ }
+ $content = $message->content->text;
🧹 Nitpick comments (4)
src/McpServer/Prompt/Extension/TemplateResolver.php (4)

105-114: Consider preserving template messages structure with prompt content.

Currently, if the prompt has any messages, template messages are completely ignored. This may not be the intended behavior in all cases, especially when templates provide structure that should be preserved.

Consider a more sophisticated merging strategy that might combine messages from both sources or allow configuration of how merging should work:

private function mergeMessages(array $promptMessages, array $templateMessages, array $arguments): array
{
    // If the prompt has no messages, use the template's messages with substitution
    if (empty($promptMessages)) {
        return $this->substituteMessages($templateMessages, $arguments);
    }

-   // Otherwise, keep the prompt's messages (extensions just provide structure)
-   return $promptMessages;
+   // Depending on your use case, you might want to consider different merging strategies:
+   
+   // Option 1: Apply variable substitution to prompt messages
+   return $this->substituteMessages($promptMessages, $arguments);
+   
+   // Option 2: Append template messages after prompt messages
+   // return array_merge($promptMessages, $this->substituteMessages($templateMessages, $arguments));
+   
+   // Option 3: Keep current behavior
+   // return $promptMessages;
}

59-65: Enhance exception message with prompt ID information.

When a template is not found, it would be helpful to include the ID of the prompt that was attempting to extend it to assist with debugging.

try {
    $template = $this->promptProvider->get($extension->templateId);
} catch (\InvalidArgumentException $e) {
    throw new TemplateResolutionException(
-       \sprintf('Template "%s" not found', $extension->templateId),
+       \sprintf('Template "%s" not found while resolving prompt "%s"', $extension->templateId, $prompt->id),
        previous: $e,
    );
}

67-72: Add logging when a non-template prompt is referenced as a template.

When a prompt is incorrectly referenced as a template, it would be helpful to log additional information for debugging purposes.

// Ensure it's a template
if ($template->type !== PromptType::Template) {
+   $this->logger?->warning('Prompt referenced as template has incorrect type', [
+       'template_id' => $extension->templateId,
+       'actual_type' => $template->type->name,
+   ]);
    throw new TemplateResolutionException(
        \sprintf('Prompt "%s" is not a template', $extension->templateId),
    );
}

47-53: Implement more descriptive circular dependency detection.

The current circular dependency detection only tells you that a circular dependency was detected, but not what the dependency chain looks like.

Include the dependency chain in the error message for better debugging:

foreach ($prompt->extensions as $extension) {
    // Prevent circular dependencies
    if (\in_array($extension->templateId, $processedExtensions, true)) {
+       $dependencyChain = implode(' -> ', array_merge($processedExtensions, [$extension->templateId]));
        throw new TemplateResolutionException(
-           \sprintf('Circular dependency detected for template "%s"', $extension->templateId),
+           \sprintf('Circular dependency detected: %s', $dependencyChain),
        );
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea042a6 and 7ae8782.

📒 Files selected for processing (3)
  • src/McpServer/Prompt/Extension/TemplateResolver.php (1 hunks)
  • src/McpServer/Prompt/PromptRegistry.php (3 hunks)
  • src/McpServer/Prompt/PromptRegistryInterface.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/McpServer/Prompt/PromptRegistryInterface.php
  • src/McpServer/Prompt/PromptRegistry.php
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/McpServer/Prompt/Extension/TemplateResolver.php (7)
src/Lib/Variable/VariableReplacementProcessor.php (1)
  • VariableReplacementProcessor (15-70)
src/Lib/Variable/VariableResolver.php (4)
  • VariableResolver (10-39)
  • __construct (12-14)
  • resolve (27-38)
  • with (16-22)
src/McpServer/Prompt/Extension/PromptExtension.php (1)
  • __construct (16-19)
src/McpServer/Prompt/Extension/PromptExtensionArgument.php (1)
  • __construct (12-15)
src/McpServer/Prompt/Extension/PromptDefinition.php (2)
  • __construct (13-21)
  • PromptDefinition (11-70)
src/McpServer/Prompt/Extension/PromptExtensionVariableProvider.php (3)
  • __construct (22-25)
  • get (32-35)
  • PromptExtensionVariableProvider (12-53)
src/McpServer/Prompt/PromptProviderInterface.php (1)
  • get (24-24)
🔇 Additional comments (1)
src/McpServer/Prompt/Extension/TemplateResolver.php (1)

36-95: Overall solid implementation of the template resolution mechanism.

The template resolver is well-structured with clear separation of concerns between the main resolution logic and the message manipulation functions. The implementation correctly handles nested templates and prevents circular dependencies.

Comment on lines +129 to +131

// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate extension arguments before creating the variable provider.

The code doesn't validate that the elements in the $arguments array are instances of PromptExtensionArgument before passing them to the PromptExtensionVariableProvider constructor.

Add validation:

// Create a variable provider for the extension arguments
+ // Validate arguments
+ foreach ($arguments as $index => $argument) {
+     if (!$argument instanceof PromptExtensionArgument) {
+         $this->logger?->warning('Invalid argument type in array', [
+             'index' => $index,
+             'type' => get_debug_type($argument),
+         ]);
+         // Skip invalid arguments or throw an exception based on your error handling strategy
+     }
+ }
$variableProvider = new PromptExtensionVariableProvider($arguments);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));
// Create a variable provider for the extension arguments
// Validate arguments
foreach ($arguments as $index => $argument) {
if (!$argument instanceof PromptExtensionArgument) {
$this->logger?->warning('Invalid argument type in array', [
'index' => $index,
'type' => get_debug_type($argument),
]);
// Skip invalid arguments or throw an exception based on your error handling strategy
}
}
$variableProvider = new PromptExtensionVariableProvider($arguments);
// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));

Comment on lines +123 to +151
private function substituteMessages(array $messages, array $arguments): array
{
$result = [];

// Create a variable provider for the extension arguments
$variableProvider = new PromptExtensionVariableProvider($arguments);

// Create a resolver with the variable provider
$resolver = $this->variableResolver->with(new VariableReplacementProcessor($variableProvider, $this->logger));

foreach ($messages as $message) {
\assert($message->content instanceof TextContent);
$content = $message->content->text;
$substitutedContent = $resolver->resolve($content);

$this->logger?->debug('Template message processed', [
'original' => $content,
'resolved' => $substitutedContent,
]);

// Create a new message with the substituted content
$result[] = new PromptMessage(
role: $message->role,
content: new TextContent(text: $substitutedContent),
);
}

return $result;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for message array elements.

The method assumes all elements in the $messages array will have a content property that's a TextContent instance, but this isn't validated before iteration.

Add validation at the beginning of the method:

private function substituteMessages(array $messages, array $arguments): array
{
    $result = [];

+   // Validate message array elements
+   foreach ($messages as $index => $message) {
+       if (!$message instanceof PromptMessage) {
+           $this->logger?->warning('Invalid message type in array', [
+               'index' => $index,
+               'type' => get_debug_type($message),
+           ]);
+           continue;
+       }
+   }

    // Create a variable provider for the extension arguments
    $variableProvider = new PromptExtensionVariableProvider($arguments);

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@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: 2

♻️ Duplicate comments (2)
src/McpServer/Prompt/Extension/TemplateResolver.php (2)

121-125: Validate extension arguments before creating the variable provider.

The code doesn't validate that the elements in the $arguments array are instances of PromptExtensionArgument before passing them to the PromptExtensionVariableProvider constructor.

Add validation:

// Create a variable provider for the extension arguments
+ // Validate arguments
+ foreach ($arguments as $index => $argument) {
+     if (!$argument instanceof PromptExtensionArgument) {
+         $this->logger?->warning('Invalid argument type in array', [
+             'index' => $index,
+             'type' => get_debug_type($argument),
+         ]);
+         // Skip invalid arguments or throw an exception based on your error handling strategy
+     }
+ }
$variableProvider = new PromptExtensionVariableProvider($arguments);

127-136: ⚠️ Potential issue

Check for content type safety before accessing text property.

The assertion \assert($message->content instanceof TextContent) provides a type check in development, but assertions may be disabled in production. This matches the pattern that was flagged by static analysis in a previous review comment.

I recommend using explicit type checking with error handling:

foreach ($messages as $message) {
-    \assert($message->content instanceof TextContent);
-    $content = $message->content->text;
+    if (!$message->content instanceof TextContent) {
+        $this->logger?->warning('Expected TextContent, got different content type', [
+            'content_type' => get_debug_type($message->content),
+        ]);
+        continue;
+    }
+    $content = $message->content->text;
     $substitutedContent = $resolver->resolve($content);

     $this->logger?->debug('Template message processed', [
         'original' => $content,
         'resolved' => $substitutedContent,
     ]);
🧹 Nitpick comments (21)
tests/fixtures/McpServer/Tool/http_tools.yaml (1)

21-21: Remove trailing whitespace.

There's a trailing space at the end of this line.

-          
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 21-21: trailing spaces

(trailing-spaces)

tests/fixtures/McpServer/Tool/error_tools.yaml (2)

11-11: Remove trailing whitespace.

There's a trailing space at the end of this line.

-          
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 11-11: trailing spaces

(trailing-spaces)


19-19: Remove trailing whitespace.

There's a trailing space at the end of this line.

-          
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 19-19: trailing spaces

(trailing-spaces)

tests/src/context.yaml (1)

26-40: Add newline at end of file.

The file is missing a newline character at the end, which is a best practice for text files.

          - ../../../fixtures/Compiler
-        showTreeView: true
+        showTreeView: true
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 40-40: no new line character at the end of file

(new-line-at-end-of-file)

tests/src/Feature/Source/File/AbstractFileSourceTestCase.php (1)

47-49: Consider a more direct approach for retrieving source type.

The current implementation serializes and deserializes an object just to extract the type property. This approach is inefficient and potentially fragile if serialization format changes.

protected function getSourceType(SourceInterface $source): string
{
-    return \json_decode(\json_encode($source), true)['type'] ?? '';
+    // If SourceInterface has a getType() method, use it directly
+    if (method_exists($source, 'getType')) {
+        return $source->getType();
+    }
+    
+    // Fallback to reflection to access protected/private type property
+    try {
+        $reflection = new \ReflectionProperty($source, 'type');
+        $reflection->setAccessible(true);
+        return $reflection->getValue($source) ?? '';
+    } catch (\ReflectionException $e) {
+        return \json_decode(\json_encode($source), true)['type'] ?? '';
+    }
}
tests/src/Feature/McpServer/Prompt/JsonSerializationTest.php (1)

22-87: Refactor long test method into smaller focused tests.

The assertConfigItems method is quite long and tests multiple different aspects of prompt serialization. Breaking it down into separate test methods would improve readability and maintainability.

-protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
+protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
+{
+    $this->assertCompletePromptSerialization($config);
+    $this->assertMinimalTemplateSerialization($config);
+    $this->assertExtensionPromptSerialization($config);
+    $this->assertRegistrySerialization($config);
+}
+
+/**
+ * Tests serialization of a complete prompt with all fields
+ */
+private function assertCompletePromptSerialization(ConfigRegistryAccessor $config): void
{
    // Test serialization of complete prompt
    $completePrompt = $config->getPrompts()->get('complete-prompt');
    $this->assertInstanceOf(PromptDefinition::class, $completePrompt);

    $serialized = \json_encode($completePrompt);
    $this->assertNotFalse($serialized, 'JSON serialization failed');

    $decoded = \json_decode($serialized, true);
    $this->assertIsArray($decoded);

    // Check that all important fields are present in the serialized output
    $this->assertArrayHasKey('id', $decoded);
    $this->assertArrayHasKey('type', $decoded);
    $this->assertArrayHasKey('description', $decoded);
    $this->assertArrayHasKey('schema', $decoded);
    $this->assertArrayHasKey('messages', $decoded);

    // Check schema structure
    $this->assertArrayHasKey('properties', $decoded['schema']);
    $this->assertArrayHasKey('required', $decoded['schema']);

    // Check that required field contains 'language'
    $this->assertContains('language', $decoded['schema']['required']);
+}
+
+/**
+ * Tests serialization of a minimal template
+ */
+private function assertMinimalTemplateSerialization(ConfigRegistryAccessor $config): void
+{
    // Test serialization of minimal template
    $minimalTemplate = $config->getPrompts()->get('minimal-template');
    $serializedTemplate = \json_encode($minimalTemplate);
    $this->assertNotFalse($serializedTemplate, 'Template JSON serialization failed');

    $decodedTemplate = \json_decode($serializedTemplate, true);
    $this->assertIsArray($decodedTemplate);

    // Check that type is set to 'template'
    $this->assertSame('template', $decodedTemplate['type']);
+}
+
+/**
+ * Tests serialization of a prompt that extends a template
+ */
+private function assertExtensionPromptSerialization(ConfigRegistryAccessor $config): void
+{
    // Test serialization of extension-only prompt
    $extensionPrompt = $config->getPrompts()->get('extension-only-prompt');
    $serializedExtension = \json_encode($extensionPrompt);
    $this->assertNotFalse($serializedExtension, 'Extension prompt JSON serialization failed');

    $decodedExtension = \json_decode($serializedExtension, true);
    $this->assertIsArray($decodedExtension);

    // Check that extend field is present and properly formatted
    $this->assertArrayHasKey('extend', $decodedExtension);
    $this->assertIsArray($decodedExtension['extend']);
    $this->assertCount(1, $decodedExtension['extend']);
    $this->assertSame('minimal-template', $decodedExtension['extend'][0]['id']);
+}
+
+/**
+ * Tests registry serialization excludes templates
+ */
+private function assertRegistrySerialization(ConfigRegistryAccessor $config): void
+{
    // Test registry serialization
    $registry = $config->getPrompts();
    $serializedRegistry = \json_encode($registry);
    $this->assertNotFalse($serializedRegistry, 'Registry JSON serialization failed');

    $decodedRegistry = \json_decode($serializedRegistry, true);
    $this->assertIsArray($decodedRegistry);
    $this->assertArrayHasKey('prompts', $decodedRegistry);

    // The registry should only serialize regular prompts, not templates
    $promptsInSerialized = $decodedRegistry['prompts'];
    foreach ($promptsInSerialized as $prompt) {
        $this->assertNotEquals('template', $prompt['type'] ?? 'prompt');
    }
}
tests/src/Feature/Compiler/AbstractCompilerTestCase.php (1)

29-167: Consider extracting expected test outputs to fixture files.

The method contains large hardcoded string literals for expected test outputs, which can make maintenance difficult. Moving these to external fixture files would improve readability and maintainability.

protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
{
    $outputPaths = [];
    $results = [];

    foreach ($config->getDocuments() as $document) {
        $outputPaths[] = $this->getContextsDir($document->outputPath);
        $compiledDocument = $compiler->compile($document);

        $results[$document->outputPath] = (string) $compiledDocument->content;
    }

    foreach ($outputPaths as $outputPath) {
        $this->assertFileExists($outputPath);
    }

-    $this->assertSame(
-        <<<'CONTENT'
-            # Project structure
-            ###  
-            ```
-            └── src/ [309.0 B, 309 chars]
-                └── dir1/ [217.0 B, 217 chars]
-                    ├── Test1Class.php [68.0 B, 68 chars]
-                    ├── TestClass.php [67.0 B, 67 chars]
-                    ├── file.txt [7.0 B, 7 chars]
-                    ├── subdir1/ [75.0 B, 75 chars]
-                    │   └── Test3Class.php [68.0 B, 68 chars]
-                    │   └── file.txt [7.0 B, 7 chars]
-                └── dir2/ [92.0 B, 92 chars]
-                    └── Test2Class.php [85.0 B, 85 chars]
-                    └── file.txt [7.0 B, 7 chars]
-            
-            ```
-            CONTENT,
-        $results['structure.md'],
-    );
+    $this->assertSame(
+        $this->getExpectedOutput('structure.md'),
+        $results['structure.md']
+    );

-    $this->assertSame(
-        <<<'CONTENT'
-            # This is a test document
-            ```
-            // Structure of documents
-            └── src/
-                └── dir1/
-                    └── Test1Class.php
-                    └── TestClass.php
-                    └── subdir1/
-                        └── Test3Class.php
-            
-            ```
-            ###  Path: `/src/dir1/Test1Class.php`
-            
-            ```php
-            <?php
-            
-            declare(strict_types=1);
-            
-            final readonly class Test1Class {}
-            
-            ```
-            ###  Path: `/src/dir1/TestClass.php`
-            
-            ```php
-            <?php
-            
-            declare(strict_types=1);
-            
-            final readonly class TestClass {}
-            
-            ```
-            ###  Path: `/src/dir1/subdir1/Test3Class.php`
-            
-            ```php
-            <?php
-            
-            declare(strict_types=1);
-            
-            final readonly class Test3Class {}
-            
-            ```
-            <INSTRUCTION>
-            Foo Bar
-            </INSTRUCTION>
-            ------------------------------------------------------------
-            CONTENT,
-        $results['test-document.md'],
-    );
+    $this->assertSame(
+        $this->getExpectedOutput('test-document.md'),
+        $results['test-document.md']
+    );

-    $this->assertSame(
-        <<<'CONTENT'
-            # This is a test document 1
-            ```
-            // Structure of documents
-            └── src/
-                └── dir2/
-                    └── Test2Class.php
-            
-            ```
-            ###  Path: `/src/dir2/Test2Class.php`
-            
-            ```php
-            <?php
-            
-            declare(strict_types=1);
-            
-            namespace dir2;
-            
-            final readonly class Test2Class {}
-            
-            ```
-            <INSTRUCTION>
-            Foo Bar
-            </INSTRUCTION>
-            ------------------------------------------------------------
-            CONTENT,
-        $results['test-document1.md'],
-    );
+    $this->assertSame(
+        $this->getExpectedOutput('test-document1.md'),
+        $results['test-document1.md']
+    );

-    $this->assertSame(
-        <<<'CONTENT'
-            # This is a test document 2
-            ```
-            // Structure of documents
-            └── src/
-                └── dir1/
-                    ├── file.txt
-                    ├── subdir1/
-                    │   └── file.txt
-                └── dir2/
-                    └── file.txt
-            
-            ```
-            ###  Path: `/src/dir1/file.txt`
-            
-            ```txt
-            foo baf
-            ```
-            ###  Path: `/src/dir1/subdir1/file.txt`
-            
-            ```txt
-            foo bar
-            ```
-            ###  Path: `/src/dir2/file.txt`
-            
-            ```txt
-            foo baz
-            ```
-            <INSTRUCTION>
-            Foo Bar
-            </INSTRUCTION>
-            ------------------------------------------------------------
-            CONTENT,
-        $results['test-document2.md'],
-    );
+    $this->assertSame(
+        $this->getExpectedOutput('test-document2.md'),
+        $results['test-document2.md']
+    );
}

+/**
+ * Gets the expected output for a given document
+ *
+ * @param string $documentName The document name
+ * @return string The expected output
+ */
+private function getExpectedOutput(string $documentName): string
+{
+    $expectedOutputFile = $this->getFixturesDir('Compiler/ExpectedOutputs') . '/' . $documentName;
+    
+    if (!file_exists($expectedOutputFile)) {
+        throw new \RuntimeException("Expected output file not found: {$expectedOutputFile}");
+    }
+    
+    return file_get_contents($expectedOutputFile);
+}
src/McpServer/Prompt/Extension/PromptDefinition.php (2)

34-59: Add return type annotation to the jsonSerialize method.

While the implementation is correct, adding a specific return type annotation would improve type safety and code clarity.

-    public function jsonSerialize(): array
+    public function jsonSerialize(): array

You're correctly filtering out null and empty values, which keeps the JSON output clean and consistent.


61-80: Consider adding more specific array type annotation.

The PHPDoc comment for serializeExtensions() uses array<mixed> which is quite generic. A more specific type annotation would improve code clarity and IDE support.

-     * @return array<mixed>|null The serialized extensions or null if empty
+     * @return array<int, array{id: string, arguments: array<string, mixed>}>|null The serialized extensions or null if empty
tests/src/Feature/McpServer/Prompt/TemplatePromptTest.php (1)

16-59: Add test cases for error conditions.

Consider adding tests for error conditions such as:

  • Extending a non-existent template
  • Circular dependencies between templates
  • Missing required template variables

This would ensure robust error handling in the template resolution system.

tests/src/Feature/McpServer/Tool/ToolHandlerTest.php (2)

171-173: Enhance HTTP tool result validation.

The current assertion only checks that the result contains "success". Consider adding more thorough validations:

-        // Verify the result
-        $this->assertStringContainsString('success', $result['output']);
+        // Verify the result
+        $this->assertStringContainsString('success', $result['output']);
+        $this->assertTrue($result['success']);
+        $this->assertArrayHasKey('requests', $result);
+        $this->assertCount(1, $result['requests']);
+        $this->assertSame('GET', $result['requests'][0]['method']);
+        $this->assertStringContainsString('example.com/api/test', $result['requests'][0]['url']);

29-173: Add test cases for error handling scenarios.

The current tests only verify the happy path. Consider adding tests for:

  • HTTP request failures (network errors, 4xx/5xx responses)
  • Command execution errors (non-zero exit codes)
  • Missing required variables in templates

This would ensure the tool handlers properly handle error conditions.

tests/docs/feature-test-guidelines.md (2)

34-58: Comprehensive guide for creating test fixtures.

The section covers important aspects:

  • Fixture placement and organization
  • Schema validation
  • Example YAML structure

However, there's a minor grammatical issue in line 41.

-- Use the JSON schema from the project to ensure your YAML is valid
-- Include a variety of configurations to test different edge cases
+- Use the JSON schema from the project to ensure your YAML is valid
+- Include a variety of configurations to test different edge cases

Consider adding a dash before "Include" to maintain consistent list formatting.

🧰 Tools
🪛 LanguageTool

[grammar] ~41-~41: It seems that “to” is missing before the verb.
Context: ... project to ensure your YAML is valid - Include a variety of configurations to test dif...

(MISSING_TO_BETWEEN_BE_AND_VB)


[style] ~41-~41: Consider using a synonym to be more concise.
Context: ... to ensure your YAML is valid - Include a variety of configurations to test different edge c...

(A_VARIETY_OF)


1-239: Add section on test debugging and troubleshooting.

Consider adding a section on debugging failing tests, including:

  • Tips for interpreting common failure patterns
  • How to troubleshoot configuration loading issues
  • Debugging variable resolution problems
  • Handling unexpected exceptions

This would make the document more complete as a reference guide.

🧰 Tools
🪛 LanguageTool

[grammar] ~41-~41: It seems that “to” is missing before the verb.
Context: ... project to ensure your YAML is valid - Include a variety of configurations to test dif...

(MISSING_TO_BETWEEN_BE_AND_VB)


[style] ~41-~41: Consider using a synonym to be more concise.
Context: ... to ensure your YAML is valid - Include a variety of configurations to test different edge c...

(A_VARIETY_OF)


[style] ~80-~80: Try using a synonym here to strengthen your writing.
Context: ...ConfigRegistryAccessor instance which gives you access to the parsed configuration ```...

(GIVE_PROVIDE)

src/McpServer/Prompt/Extension/TemplateResolver.php (3)

102-108: Simplify array merging operation.

The current loop structure to merge arrays can be simplified to improve readability.

Consider using array_merge() or the spread operator:

- foreach ($this->substituteMessages($templateMessages, $arguments) as $templateMessage) {
-     $promptMessages[] = $templateMessage;
- }
- 
- // Otherwise, keep the prompt's messages (extensions just provide structure)
- return $promptMessages;
+ // Merge template messages (with substitution) with the prompt's messages
+ return array_merge($promptMessages, $this->substituteMessages($templateMessages, $arguments));

47-62: Enhance circular dependency detection to prevent indirect cycles.

The current circular dependency check only prevents direct circular dependencies (A extends A) but might not catch indirect ones (A extends B extends A).

Consider tracking the entire ancestry chain instead of just processed IDs:

public function resolve(PromptDefinition $prompt): PromptDefinition
{
    // If no extensions, return the prompt as is
    if (empty($prompt->extensions)) {
        return $prompt;
    }

    // Process each extension
    $messages = $prompt->messages;
-    $processedExtensions = [];
+    return $this->resolveWithAncestry($prompt, $messages, []);
+}
+
+/**
+ * Resolves a prompt definition with ancestry tracking to prevent circular dependencies.
+ *
+ * @param PromptDefinition $prompt The prompt definition to resolve
+ * @param array $messages Current accumulated messages
+ * @param array $ancestry Chain of template IDs in the current resolution path
+ * @return PromptDefinition The resolved prompt definition
+ * @throws TemplateResolutionException If the resolution fails
+ */
+private function resolveWithAncestry(PromptDefinition $prompt, array $messages, array $ancestry): PromptDefinition
+{
+    // Add current prompt to ancestry
+    $ancestry[] = $prompt->id;

    foreach ($prompt->extensions as $extension) {
        // Prevent circular dependencies
-        if (\in_array($extension->templateId, $processedExtensions, true)) {
+        if (\in_array($extension->templateId, $ancestry, true)) {
+            $this->logger?->warning('Circular dependency detected', [
+                'template_id' => $extension->templateId,
+                'ancestry' => $ancestry,
+            ]);
            continue;
        }

-        $processedExtensions[] = $extension->templateId;

35-85: Consider adding method to resolve multiple prompts efficiently.

The current design resolves templates one at a time, which could lead to redundant resolutions if multiple prompts extend the same templates.

Consider adding a method to resolve multiple prompts efficiently with template caching:

/**
 * Resolves multiple prompts efficiently by caching resolved templates.
 *
 * @param PromptDefinition[] $prompts The prompts to resolve
 * @return PromptDefinition[] The resolved prompts
 * @throws TemplateResolutionException If resolution fails
 */
public function resolveMany(array $prompts): array
{
    $templateCache = [];
    $result = [];

    foreach ($prompts as $prompt) {
        $result[] = $this->resolveWithCache($prompt, $templateCache);
    }

    return $result;
}

/**
 * Resolves a prompt with template caching.
 *
 * @param PromptDefinition $prompt The prompt to resolve
 * @param array $templateCache Cache of already resolved templates
 * @return PromptDefinition The resolved prompt
 */
private function resolveWithCache(PromptDefinition $prompt, array &$templateCache): PromptDefinition
{
    // Implementation that uses and updates the cache
    // ...
}
tests/src/Feature/FeatureTestCases.php (4)

36-45: Add error handling for directory deletion in tearDown.

The current implementation doesn't check if the directory deletion was successful, which could leave test artifacts behind in case of failure.

Consider adding error handling:

protected function tearDown(): void
{
    parent::tearDown();

    if (\is_dir($this->getContextsDir())) {
        $files = new Files();
-        $files->deleteDirectory($this->getContextsDir());
+        try {
+            $files->deleteDirectory($this->getContextsDir());
+        } catch (\Exception $e) {
+            $this->addWarning(sprintf('Failed to clean up context directory: %s', $e->getMessage()));
+        }
    }
}

47-50: Simplify path construction in getContextsDir method.

The current path creation is a bit verbose and could be simplified for better readability.

Consider using a more direct approach:

protected function getContextsDir(string $path = ''): string
{
-    return (string) FSPath::create($this->getFixturesDir('Compiler/.context'))->join($path);
+    $contextDir = $this->getFixturesDir('Compiler/.context');
+    return $path ? "$contextDir/$path" : $contextDir;
}

59-64: Add error handling for configuration loading failures.

The current implementation doesn't have error handling if the configuration fails to load, which could lead to unclear test failures.

Consider adding error handling:

private function compileDocuments(DocumentCompiler $compiler, ConfigurationProvider $configProvider): void
{
-    $loader = $configProvider->fromPath($this->getConfigPath());
+    try {
+        $loader = $configProvider->fromPath($this->getConfigPath());
+        $this->assertConfigItems($compiler, new ConfigRegistryAccessor($loader->load()));
+    } catch (\Exception $e) {
+        $this->fail(sprintf('Failed to load configuration: %s', $e->getMessage()));
+    }
-    $this->assertConfigItems($compiler, new ConfigRegistryAccessor($loader->load()));
}

25-34: Consider adding test annotations for better test organization.

The compile() method currently has only a basic #[Test] annotation, which doesn't provide much information about the test purpose.

Consider adding more descriptive annotations:

-#[Test]
+#[Test]
+#[\PHPUnit\Framework\Attributes\Group('feature')]
+#[\PHPUnit\Framework\Attributes\DisplayName('Compiles documents with configuration')]
public function compile(): void
{
    $this->getContainer()->runScope(
        bindings: new Scope(
            name: AppScope::Compiler,
        ),
        scope: $this->compileDocuments(...),
    );
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ae8782 and 96fe621.

📒 Files selected for processing (40)
  • src/Document/DocumentRegistry.php (2 hunks)
  • src/McpServer/Action/Prompts/GetPromptAction.php (1 hunks)
  • src/McpServer/Prompt/Extension/PromptDefinition.php (1 hunks)
  • src/McpServer/Prompt/Extension/TemplateResolver.php (1 hunks)
  • src/McpServer/Prompt/PromptMessageProcessor.php (1 hunks)
  • src/McpServer/Prompt/PromptProviderInterface.php (2 hunks)
  • src/McpServer/Prompt/PromptRegistry.php (6 hunks)
  • tests/docs/feature-test-guidelines.md (1 hunks)
  • tests/fixtures/McpServer/Prompts/basic_prompts.yaml (1 hunks)
  • tests/fixtures/McpServer/Prompts/json_serialization.yaml (1 hunks)
  • tests/fixtures/McpServer/Prompts/prompt_schema.yaml (1 hunks)
  • tests/fixtures/McpServer/Prompts/registry_operations.yaml (1 hunks)
  • tests/fixtures/McpServer/Prompts/templates.yaml (1 hunks)
  • tests/fixtures/McpServer/Tool/basic_tools.yaml (1 hunks)
  • tests/fixtures/McpServer/Tool/error_tools.yaml (1 hunks)
  • tests/fixtures/McpServer/Tool/http_tools.yaml (1 hunks)
  • tests/fixtures/McpServer/Tool/run_tools.yaml (1 hunks)
  • tests/fixtures/Source/File/basic_file_source.yaml (1 hunks)
  • tests/fixtures/Source/File/filtered_file_source.yaml (1 hunks)
  • tests/fixtures/Source/File/invalid_file_source.yaml (1 hunks)
  • tests/fixtures/Source/File/test_files/test_directory/nested_file.txt (1 hunks)
  • tests/fixtures/Source/File/test_files/test_file.txt (1 hunks)
  • tests/fixtures/Source/File/tree_view_file_source.yaml (1 hunks)
  • tests/src/Feature/Compiler/AbstractCompilerTestCase.php (1 hunks)
  • tests/src/Feature/FeatureTestCases.php (1 hunks)
  • tests/src/Feature/McpServer/Prompt/BasicPromptTest.php (1 hunks)
  • tests/src/Feature/McpServer/Prompt/JsonSerializationTest.php (1 hunks)
  • tests/src/Feature/McpServer/Prompt/RegistryOperationsTest.php (1 hunks)
  • tests/src/Feature/McpServer/Prompt/SchemaPromptTest.php (1 hunks)
  • tests/src/Feature/McpServer/Prompt/TemplatePromptTest.php (1 hunks)
  • tests/src/Feature/McpServer/Tool/BasicToolConfigTest.php (1 hunks)
  • tests/src/Feature/McpServer/Tool/HttpToolTest.php (1 hunks)
  • tests/src/Feature/McpServer/Tool/RunToolTest.php (1 hunks)
  • tests/src/Feature/McpServer/Tool/ToolHandlerTest.php (1 hunks)
  • tests/src/Feature/Source/File/AbstractFileSourceTestCase.php (1 hunks)
  • tests/src/Feature/Source/File/FileSourceFilterTest.php (1 hunks)
  • tests/src/Feature/Source/File/FileSourceSerializationTest.php (1 hunks)
  • tests/src/Feature/Source/File/FileSourceTest.php (1 hunks)
  • tests/src/Feature/Source/File/FileSourceTreeViewTest.php (1 hunks)
  • tests/src/context.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (12)
  • tests/fixtures/Source/File/basic_file_source.yaml
  • tests/fixtures/Source/File/invalid_file_source.yaml
  • tests/fixtures/Source/File/filtered_file_source.yaml
  • tests/fixtures/McpServer/Tool/basic_tools.yaml
  • tests/fixtures/Source/File/tree_view_file_source.yaml
  • tests/fixtures/McpServer/Tool/run_tools.yaml
  • tests/fixtures/McpServer/Prompts/registry_operations.yaml
  • tests/fixtures/McpServer/Prompts/prompt_schema.yaml
  • tests/fixtures/Source/File/test_files/test_directory/nested_file.txt
  • tests/fixtures/McpServer/Prompts/basic_prompts.yaml
  • tests/fixtures/McpServer/Prompts/templates.yaml
  • tests/fixtures/McpServer/Prompts/json_serialization.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/McpServer/Prompt/PromptProviderInterface.php
🧰 Additional context used
🧬 Code Graph Analysis (2)
tests/src/Feature/McpServer/Tool/BasicToolConfigTest.php (5)
src/Config/Registry/ConfigRegistryAccessor.php (1)
  • ConfigRegistryAccessor (15-73)
src/McpServer/Tool/Config/ToolDefinition.php (1)
  • ToolDefinition (10-146)
tests/src/Feature/FeatureTestCases.php (1)
  • FeatureTestCases (17-65)
tests/src/TestCase.php (1)
  • getFixturesDir (62-71)
src/McpServer/Tool/Config/ToolSchema.php (1)
  • getProperties (54-57)
tests/src/Feature/McpServer/Tool/ToolHandlerTest.php (5)
src/Lib/Variable/VariableResolver.php (2)
  • VariableResolver (10-39)
  • with (16-22)
src/McpServer/Tool/Config/ToolArg.php (1)
  • ToolArg (10-77)
src/McpServer/Tool/Config/ToolCommand.php (1)
  • ToolCommand (10-109)
src/McpServer/Tool/Config/ToolDefinition.php (1)
  • ToolDefinition (10-146)
tests/src/AppTestCase.php (1)
  • AppTestCase (12-99)
🪛 GitHub Check: psalm (ubuntu-latest, 8.3, locked)
src/Document/DocumentRegistry.php

[failure] 12-12: MissingTemplateParam
src/Document/DocumentRegistry.php:12:60: MissingTemplateParam: Butschster\ContextGenerator\Document\DocumentRegistry has missing template params when extending ArrayAccess, expecting 2 (see https://psalm.dev/182)

🪛 GitHub Actions: 🔍 Static analysis
src/Document/DocumentRegistry.php

[error] 12-12: Psalm: Missing template parameters when extending ArrayAccess, expecting 2 template params (MissingTemplateParam). See https://psalm.dev/182

🪛 LanguageTool
tests/docs/feature-test-guidelines.md

[grammar] ~41-~41: It seems that “to” is missing before the verb.
Context: ... project to ensure your YAML is valid - Include a variety of configurations to test dif...

(MISSING_TO_BETWEEN_BE_AND_VB)


[style] ~41-~41: Consider using a synonym to be more concise.
Context: ... to ensure your YAML is valid - Include a variety of configurations to test different edge c...

(A_VARIETY_OF)


[style] ~80-~80: Try using a synonym here to strengthen your writing.
Context: ...ConfigRegistryAccessor instance which gives you access to the parsed configuration ```...

(GIVE_PROVIDE)

tests/fixtures/Source/File/test_files/test_file.txt

[grammar] ~1-~1: Consider using either the past participle “tested” or the present participle “testing” here.
Context: This is test content for the file source test.

(BEEN_PART_AGREEMENT)

🪛 YAMLlint (1.35.1)
tests/fixtures/McpServer/Tool/error_tools.yaml

[error] 11-11: trailing spaces

(trailing-spaces)


[error] 19-19: trailing spaces

(trailing-spaces)


[error] 24-24: trailing spaces

(trailing-spaces)

tests/fixtures/McpServer/Tool/http_tools.yaml

[error] 21-21: trailing spaces

(trailing-spaces)

tests/src/context.yaml

[error] 40-40: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (76)
tests/fixtures/Source/File/test_files/test_file.txt (1)

1-1: Simple, appropriate test content.

The file content serves its purpose as a test fixture for file source tests.

🧰 Tools
🪛 LanguageTool

[grammar] ~1-~1: Consider using either the past participle “tested” or the present participle “testing” here.
Context: This is test content for the file source test.

(BEEN_PART_AGREEMENT)

tests/fixtures/McpServer/Tool/http_tools.yaml (2)

4-20: Well-structured HTTP GET tool configuration.

The HTTP GET tool is properly defined with schema validation for endpoint and token parameters, and correctly uses variable substitution syntax for dynamic values.


22-37: Well-structured HTTP POST tool configuration.

The HTTP POST tool is properly defined with appropriate headers, request method, and body structure. The variable substitution in the body is correctly implemented.

tests/fixtures/McpServer/Tool/error_tools.yaml (4)

4-10: Well-structured error test case for missing required field.

This tool definition intentionally omits the required description field, which is appropriate for testing schema validation.


12-18: Good error test case for invalid tool type.

This tool definition correctly tests the handling of an invalid tool type value.


20-24: Appropriate test case for missing commands array.

This test case correctly omits the required commands array for a run-type tool.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 24-24: trailing spaces

(trailing-spaces)


25-29: Good error test case for HTTP tool without requests.

This test case correctly omits the required requests array for an HTTP tool.

tests/src/context.yaml (2)

3-14: Well-structured prompts test document configuration.

The document group for prompts tests is properly organized with clear description, output path, and source paths.


15-25: Good tools test document configuration.

The document group for tools tests is properly organized with appropriate source paths.

src/McpServer/Action/Prompts/GetPromptAction.php (2)

32-32: Good architectural improvement passing request attributes to prompt provider.

This change properly delegates template processing to the prompt provider by passing request attributes, aligning with the new template-based prompt extension system.


35-35: Simplified response handling.

This change streamlines the response by directly using the processed messages from the prompt, which is cleaner and better aligned with the separation of concerns principle.

src/Document/DocumentRegistry.php (2)

49-57: Implementation of offsetExists and offsetGet looks good.

The read-only array access implementation is a good addition, allowing access to documents by their numeric index while maintaining the collection's integrity.


59-67: Proper protection against direct modification of the registry.

Good implementation of write-protection methods that throw appropriate exceptions when someone tries to modify the collection directly, enforcing the use of the register() method instead.

tests/src/Feature/McpServer/Tool/HttpToolTest.php (5)

13-18: LGTM! Clear test class setup.

The test class structure is clean and follows the established pattern for feature tests, with a clear method to specify the configuration path.


20-41: Well-structured test for HTTP GET tool configuration.

The test correctly verifies the basic tool properties and dives into the HTTP-specific request configuration, making appropriate assertions about the URL, method, and headers.


42-55: Good validation of schema properties for variable replacement.

This section properly verifies that the schema contains the expected parameters that can be used for variable substitution in the request URL and headers.


56-81: Comprehensive validation of HTTP POST tool configuration.

The test thoroughly validates the POST tool configuration, including headers, body structure, and schema properties.


82-86: Good verification of handler instantiation.

The test confirms that the correct handler class is instantiated for HTTP tools, which is an important aspect to validate.

tests/src/Feature/Source/File/FileSourceTest.php (4)

11-16: Well-documented test class with appropriate grouping.

The class uses PHPUnit attributes for test grouping and includes a clear docblock explaining its purpose.


18-22: LGTM! Correct override implementation.

The method is properly tagged with the #[\Override] attribute and correctly implements the abstract method from the parent class.


24-36: Good validation of source configuration properties.

The test properly verifies the source type, description, and file pattern configuration, as well as tree view settings.


37-50: Comprehensive output validation.

The test validates both document metadata and that the compiled content contains the expected text from the fixture files. This ensures both the structure and content are correctly processed.

tests/src/Feature/McpServer/Tool/BasicToolConfigTest.php (4)

12-17: LGTM! Clean test class setup.

The class extends the appropriate base class and correctly implements the method to specify the configuration path.


19-34: Good validation of basic tool properties.

The test correctly verifies the tool registry's existence and validates the essential properties of the test command tool.


35-41: Thorough command validation.

The test properly verifies the command structure, including the command name and arguments, ensuring the tool definition is parsed correctly.


42-47: Good schema property validation.

The test correctly verifies that the tool schema contains the expected property and that it has the correct type.

tests/src/Feature/McpServer/Prompt/SchemaPromptTest.php (6)

1-21: Well-structured test setup for schema-based prompts.

The class is properly organized with clear namespace, imports, and extends from the base FeatureTestCases class. The getConfigPath() method correctly points to the fixture file for testing.


22-41: Good implementation of test assertions for basic schema prompt.

The test properly retrieves the prompt definition and verifies its schema properties, including checking the argument count and initializing variables for detailed argument inspection.


43-51: Thorough validation of prompt argument properties.

The test correctly validates both required and optional arguments, checking their descriptions and required flags to ensure proper schema configuration.


52-56: Proper variable placeholder validation in prompt messages.

The test verifies that variable placeholders ({{language}} and {{experience}}) are correctly preserved in the prompt messages, which is essential for the template-based prompt extension system.


57-82: Comprehensive testing of complex schema prompt.

The test thoroughly validates a more complex prompt configuration, including argument counts, required arguments filtering, and argument name verification. Good use of array functions for processing argument collections.


83-87: Complete variable placeholder verification for complex prompt.

The test properly checks that all expected variable placeholders are present in the prompt message content, ensuring template functionality works correctly.

src/McpServer/Prompt/PromptMessageProcessor.php (2)

1-19: Well-designed processor class for handling prompt message variables.

The class is properly declared as final readonly, uses constructor property promotion, and has appropriate namespacing and imports. Good dependency injection of the VariableResolver.


20-40: Clean implementation of message processing with variable substitution.

The process method correctly:

  1. Takes a PromptDefinition and optional arguments array
  2. Maps over each message to replace variables in TextContent
  3. Creates new immutable instances rather than modifying existing ones
  4. Returns a new PromptDefinition with processed messages

This immutable approach ensures thread safety and prevents side effects.

tests/src/Feature/Source/File/FileSourceSerializationTest.php (4)

1-24: Well-structured test setup for FileSource serialization.

The class is properly organized with appropriate namespace, imports, and group attributes for test categorization. The getConfigPath() method correctly points to the basic file source fixture.


25-43: Thorough basic serialization test for FileSource.

The test properly validates JSON serialization of a file source from config, checking that the serialized object contains all required fields with correct values.


44-62: Proper validation of minimal FileSource serialization.

The test creates a FileSource with minimal parameters and verifies that optional fields are correctly omitted from the serialized output, preventing unnecessary data in the JSON.


63-98: Comprehensive testing of fully-populated FileSource serialization.

The test creates a FileSource with all possible options and verifies that all fields are properly serialized, including nested objects like TreeViewConfig and numeric values. Good coverage of edge cases.

tests/src/Feature/McpServer/Tool/RunToolTest.php (5)

1-21: Well-structured test setup for run tools.

The class is properly organized with appropriate namespace, imports, and extends from the base feature test case. The getConfigPath() method correctly points to the run tools fixture.


22-37: Proper validation of tool registry and basic tool properties.

The test correctly verifies the presence of the expected tool in the registry and validates its basic properties including id, description, and type.


38-53: Thorough testing of command arguments including variable placeholders.

The test properly validates both simple variable placeholders and conditional arguments with "when" clauses, ensuring the tool's command structure is correctly configured.


54-66: Comprehensive schema property validation.

The test verifies the schema properties including types and default values, ensuring that the tool's input validation will work correctly with both required and optional parameters.


67-71: Proper handler factory verification.

The test correctly validates that the appropriate handler class (RunToolHandler) is instantiated for the tool type, ensuring the tool execution will use the correct implementation.

src/McpServer/Prompt/PromptRegistry.php (5)

22-24: Good use of dependency injection for the processor.

Adding the PromptMessageProcessor dependency via constructor injection is a clean approach that follows dependency injection best practices and improves testability.


34-46: Nice implementation of variable substitution support.

The updated get() method now accepts an $arguments array, which enables the template variable substitution feature. The implementation correctly delegates the processing to the injected processor.


58-64: Good addition of template filtering functionality.

The new allTemplates() method provides a clean way to retrieve only template-type prompts from the registry, using array_filter with a type check. This supports the new template feature well.


79-87: Well-implemented prompt filtering.

The getItems() method has been updated to filter out templates and return only regular prompts. The use of array_values() ensures that the returned array maintains sequential numeric indexes.


89-94: Clean API approach for JSON serialization.

Excluding templates from JSON serialization keeps the API clean and focused, as mentioned in the PR objectives. The comment clearly explains the intention behind this behavior.

tests/src/Feature/McpServer/Prompt/RegistryOperationsTest.php (6)

19-22: Good test fixture organization.

Using a dedicated YAML fixture for the test provides clean separation of test data and test logic.


29-32: Thorough testing of the 'has' method.

The test properly checks both positive and negative cases for the has() method, including regular prompts, templates, and non-existent entries.


34-45: Comprehensive testing of the 'get' method.

The test verifies retrieval of both regular prompts and templates, and properly checks exception handling for non-existent prompts.


54-61: Good validation of template filtering.

The test confirms that allTemplates() correctly returns only template-type prompts and verifies the expected count and type of each template.


62-68: Proper verification of regular prompt filtering.

The test ensures that getItems() returns only regular prompts (excluding templates) and verifies the expected count and type of each item.


73-80: Complete iterator interface testing.

The test thoroughly verifies that the iterator implementation correctly yields only regular prompts and maintains the expected count.

tests/src/Feature/Source/File/FileSourceTreeViewTest.php (2)

30-38: Complete tree view configuration verification.

The test thoroughly checks all tree view configuration options, ensuring they're correctly set according to the fixture.


44-51: Good output validation strategy.

The test sensibly checks for the presence of key elements in the rendered output without being overly prescriptive about the exact format, which makes the test less brittle to formatting changes.

tests/src/Feature/Source/File/FileSourceFilterTest.php (3)

18-22: Good use of Override attribute.

Using the #[\Override] attribute explicitly indicates that this method is overriding a parent class method, improving code clarity.


31-34: Thorough filter configuration verification.

The test properly validates all aspects of the file source filter configuration, including path restrictions, exclusion patterns, and content filters.


40-43: Comprehensive filter output validation.

The test effectively verifies both inclusion and exclusion filtering by checking for the presence of expected content and the absence of filtered content.

tests/src/Feature/McpServer/Prompt/BasicPromptTest.php (1)

24-53: The test implementation looks clean and well-structured.

This test effectively validates three prompt types (minimal, multi-message, and with variables) and thoroughly checks their structure, content, and variable resolution.

tests/src/Feature/Compiler/AbstractCompilerTestCase.php (1)

9-11: Class inheritance change looks appropriate.

The refactoring to extend FeatureTestCases aligns with the PR objective of introducing a template-based system. This change likely centralizes common testing logic in the parent class.

src/McpServer/Prompt/Extension/PromptDefinition.php (3)

1-11: Well-organized class structure with appropriate namespacing.

The class is correctly placed in the namespace that reflects its role in the prompt extension system, with all necessary imports. The use of strict types is a good practice.


11-21: Good use of PHP 8 features and type safety.

The class makes excellent use of PHP 8's features:

  • final readonly ensures immutability
  • Constructor property promotion for concise code
  • Type declarations for all properties
  • Default values for optional properties
  • PHPDoc array annotations for collection properties

This implementation aligns well with the PR's objective of adding a template-based prompt extension system.


23-32: Properly implemented immutability pattern.

The withMessages method correctly follows the immutability pattern by returning a new instance with modified messages while preserving all other properties. This is essential for maintaining state consistency when working with prompt definitions.

tests/src/Feature/McpServer/Prompt/TemplatePromptTest.php (5)

1-17: Well-structured test class with clear purpose.

The test class is properly defined with strict types, appropriate namespace, and necessary imports. The class documentation clearly explains its purpose of testing template-related prompt functionality.


18-21: Correctly implemented config path method.

The getConfigPath() method properly uses the helper method to locate the test fixture file. This follows the pattern established by the feature test framework.


23-37: Comprehensive base template validation.

The test properly verifies:

  • Instance type correctness
  • Template type enumeration value
  • Message count
  • Content contains expected variables

This thoroughly validates the basic template functionality.


38-49: Thorough verification of template extension functionality.

The test correctly validates single template extension by checking:

  • Instance type
  • Prompt type enumeration
  • Extension count and reference
  • Message resolution with variable substitution

This confirms the core functionality of template extension works as expected.


50-59: Comprehensive multi-template extension testing.

The test verifies that multiple template extensions work correctly, including:

  • Proper message merging
  • Variable resolution across templates
  • Comment noting that second extension takes precedence

This confirms that the inheritance chain for templates functions as designed.

tests/src/Feature/McpServer/Tool/ToolHandlerTest.php (2)

23-85: Well-structured run tool execution test.

This test successfully validates:

  • Command execution with variable argument replacement
  • Proper construction of tool commands
  • Result verification including success status and output

The test effectively mocks the command executor and verifies both the execution process and the result format.


87-173: HTTP tool variable replacement test is implemented correctly.

The test effectively:

  • Mocks the HTTP client with appropriate callback verification
  • Sets up a variable resolver with test variables
  • Creates a tool definition with variables in URL and headers
  • Verifies that variables are correctly resolved in the request

Good use of willReturnCallback to validate the processed URL and headers.

tests/docs/feature-test-guidelines.md (5)

1-10: Excellent introduction to the feature test framework.

The document provides a clear introduction explaining the purpose and context of the FeatureTestCases abstract class.


5-32: Well-structured code example for basic test structure.

The PHP code example clearly illustrates how to extend the FeatureTestCases class and implement the required methods. The code is well-formatted and includes necessary imports.


76-99: Clear explanation of ConfigRegistryAccessor usage.

This section effectively demonstrates how to use the ConfigRegistryAccessor to retrieve and validate configuration items. The code example is clear and demonstrates common assertion patterns.

🧰 Tools
🪛 LanguageTool

[style] ~80-~80: Try using a synonym here to strengthen your writing.
Context: ...ConfigRegistryAccessor instance which gives you access to the parsed configuration ```...

(GIVE_PROVIDE)


101-138: Thorough coverage of advanced testing scenarios.

The section on error case testing and temporary files for sub-tests provides valuable guidance for testing edge cases and error conditions.


140-157: Comprehensive best practices section.

The best practices outlined provide excellent guidance for writing effective feature tests, covering:

  • Test organization and focus
  • Assertion thoroughness
  • Edge case coverage
  • Naming conventions
  • Documentation
  • Test isolation

This will help maintain test quality and consistency.

Comment on lines +57 to +74
$tempDir = $this->createTempDir();

// Create a basic structure
$rootFile = $tempDir . '/root_file.txt';
\file_put_contents($rootFile, 'Root file content');

$subDir = $tempDir . '/subdir';
\mkdir($subDir, 0777, true);

$nestedFile = $subDir . '/nested_file.txt';
\file_put_contents($nestedFile, 'Nested file content');

$deepDir = $subDir . '/deep';
\mkdir($deepDir, 0777, true);

$deepFile = $deepDir . '/deep_file.txt';
\file_put_contents($deepFile, 'Deep nested file content');

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for file operations.

The file creation operations don't include error handling. File system operations can fail due to permission issues, disk space limitations, or other environmental factors.

protected function createTestFileStructure(): string
{
    $tempDir = $this->createTempDir();

    // Create a basic structure
    $rootFile = $tempDir . '/root_file.txt';
-    \file_put_contents($rootFile, 'Root file content');
+    if (false === \file_put_contents($rootFile, 'Root file content')) {
+        throw new \RuntimeException("Failed to create root file: $rootFile");
+    }

    $subDir = $tempDir . '/subdir';
-    \mkdir($subDir, 0777, true);
+    if (!is_dir($subDir) && !mkdir($subDir, 0777, true)) {
+        throw new \RuntimeException("Failed to create directory: $subDir");
+    }

    $nestedFile = $subDir . '/nested_file.txt';
-    \file_put_contents($nestedFile, 'Nested file content');
+    if (false === \file_put_contents($nestedFile, 'Nested file content')) {
+        throw new \RuntimeException("Failed to create nested file: $nestedFile");
+    }

    $deepDir = $subDir . '/deep';
-    \mkdir($deepDir, 0777, true);
+    if (!is_dir($deepDir) && !mkdir($deepDir, 0777, true)) {
+        throw new \RuntimeException("Failed to create directory: $deepDir");
+    }

    $deepFile = $deepDir . '/deep_file.txt';
-    \file_put_contents($deepFile, 'Deep nested file content');
+    if (false === \file_put_contents($deepFile, 'Deep nested file content')) {
+        throw new \RuntimeException("Failed to create deep file: $deepFile");
+    }

    return $tempDir;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$tempDir = $this->createTempDir();
// Create a basic structure
$rootFile = $tempDir . '/root_file.txt';
\file_put_contents($rootFile, 'Root file content');
$subDir = $tempDir . '/subdir';
\mkdir($subDir, 0777, true);
$nestedFile = $subDir . '/nested_file.txt';
\file_put_contents($nestedFile, 'Nested file content');
$deepDir = $subDir . '/deep';
\mkdir($deepDir, 0777, true);
$deepFile = $deepDir . '/deep_file.txt';
\file_put_contents($deepFile, 'Deep nested file content');
protected function createTestFileStructure(): string
{
$tempDir = $this->createTempDir();
// Create a basic structure
$rootFile = $tempDir . '/root_file.txt';
if (false === \file_put_contents($rootFile, 'Root file content')) {
throw new \RuntimeException("Failed to create root file: $rootFile");
}
$subDir = $tempDir . '/subdir';
if (!is_dir($subDir) && !mkdir($subDir, 0777, true)) {
throw new \RuntimeException("Failed to create directory: $subDir");
}
$nestedFile = $subDir . '/nested_file.txt';
if (false === \file_put_contents($nestedFile, 'Nested file content')) {
throw new \RuntimeException("Failed to create nested file: $nestedFile");
}
$deepDir = $subDir . '/deep';
if (!is_dir($deepDir) && !mkdir($deepDir, 0777, true)) {
throw new \RuntimeException("Failed to create directory: $deepDir");
}
$deepFile = $deepDir . '/deep_file.txt';
if (false === \file_put_contents($deepFile, 'Deep nested file content')) {
throw new \RuntimeException("Failed to create deep file: $deepFile");
}
return $tempDir;
}

@butschster butschster merged commit a4f9eaf into main Apr 15, 2025
9 checks passed
@butschster butschster deleted the issue/180 branch April 15, 2025 06:05
@github-project-automation github-project-automation bot moved this from In review to Done in Context Generator Apr 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request mcp MCP server components
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Implement Template-based Prompt Extension System
1 participant