diff --git a/generator/docs/README.md b/generator/docs/README.md index 807edff..6ecae08 100644 --- a/generator/docs/README.md +++ b/generator/docs/README.md @@ -7,11 +7,15 @@ A TypeScript application that generates PHP 7.4 DTOs directly from the MCP TypeS The generator fetches the official MCP TypeScript schema from GitHub, parses it using ts-morph AST analysis, and produces production-quality PHP code including: - **DTOs** - Data Transfer Objects with `fromArray()`/`toArray()` methods -- **Enums** - Class-based enums (PHP 7.4 compatible) +- **Enums** - Class-based enums (PHP 7.4 compatible) from both string literal unions and TypeScript enums +- **Constants** - Protocol constants class with error codes and version strings - **Union Interfaces** - Marker interfaces for polymorphic types - **Factories** - Discriminator-based instantiation for unions +- **Type Alias Wrappers** - Concrete classes for type aliases referenced in unions +- **Intersection Type Wrappers** - Concrete classes for intersection types (A & B) - **Builders** - Optional fluent builder pattern classes - **Contracts** - Marker interfaces for type hierarchies +- **Skill Files** - Claude Code reference documentation and search tools ## Quick Start @@ -78,20 +82,114 @@ The generator produces PHP files organized by MCP domain: ``` src/ ├── Common/ -│ ├── Protocol/ # Core protocol types -│ ├── JsonRpc/ # JSON-RPC message types -│ └── Content/ # Content block types +│ ├── AbstractDataTransferObject.php +│ ├── AbstractEnum.php +│ ├── McpConstants.php # Protocol constants & error codes +│ ├── Traits/ +│ ├── Contracts/ # Marker interfaces +│ ├── Protocol/ # Core protocol types +│ ├── JsonRpc/ # JSON-RPC message types +│ ├── Content/ # Content block types +│ └── Tasks/ # Shared task types ├── Server/ -│ ├── Tools/ # Tool definitions -│ ├── Resources/ # Resource management -│ ├── Prompts/ # Prompt templates -│ └── Logging/ # Logging types +│ ├── Tools/ # Tool definitions +│ ├── Resources/ # Resource management +│ ├── Prompts/ # Prompt templates +│ ├── Logging/ # Logging types +│ ├── Lifecycle/ # Server lifecycle +│ └── Core/ # Server core types ├── Client/ -│ ├── Sampling/ # LLM sampling -│ ├── Elicitation/ # User input elicitation -│ ├── Roots/ # Root directory management -│ └── Tasks/ # Background tasks -└── Contracts/ # Shared interfaces +│ ├── Sampling/ # LLM sampling +│ ├── Elicitation/ # User input elicitation +│ ├── Roots/ # Root directory management +│ ├── Tasks/ # Background tasks +│ └── Lifecycle/ # Client lifecycle +└── Contracts/ # Shared interfaces +``` + +Additionally, skill files are generated for Claude Code integration: + +``` +skill/ +├── SKILL.md # Entry point +├── reference/ # Markdown documentation +│ ├── overview.md +│ ├── common.md +│ ├── server.md +│ ├── client.md +│ ├── rpc-methods.md +│ └── factories.md +├── data/ # JSON data files +│ ├── schema-index.json +│ ├── schema-common.json +│ ├── schema-server.json +│ └── schema-client.json +└── scripts/ # Search utilities + ├── search-types.sh + ├── get-type.sh + └── find-rpc.sh +``` + +## Key Features + +### Version Tracking + +The generator tracks schema history and annotates generated code with `@since` tags: + +```php +/** + * @since 2024-11-05 + */ +class CallToolRequest extends Request +{ + /** + * @since 2025-03-26 + */ + protected ?array $arguments; +} +``` + +### Type Alias Wrappers + +Type aliases like `type EmptyResult = Result` get wrapper classes when referenced in unions: + +```php +class EmptyResult extends Result implements ServerResultInterface, ClientResultInterface +{ + // Inherits everything from Result +} +``` + +### Intersection Type Wrappers + +Intersection types like `type GetTaskResult = Result & Task` become concrete classes: + +```php +class GetTaskResult extends Result implements ClientResultInterface +{ + // Properties from Task are merged in + protected string $taskId; + protected string $status; + // ... +} +``` + +### Protocol Constants + +Exported constants from the schema become a PHP constants class: + +```php +class McpConstants +{ + public const LATEST_PROTOCOL_VERSION = '2025-11-25'; + public const JSONRPC_VERSION = '2.0'; + public const PARSE_ERROR = -32700; + public const INVALID_REQUEST = -32600; + // ... + + public static function isValidErrorCode(int $code): bool { ... } + public static function getErrorCodeName(int $code): ?string { ... } +} ``` ## Documentation diff --git a/generator/docs/architecture.md b/generator/docs/architecture.md index d92243c..988ccda 100644 --- a/generator/docs/architecture.md +++ b/generator/docs/architecture.md @@ -11,7 +11,8 @@ generator/ │ ├── config/ │ │ └── index.ts # Configuration management │ ├── types/ -│ │ └── index.ts # TypeScript type definitions +│ │ ├── index.ts # TypeScript type definitions +│ │ └── skill-types.ts # Skill generation types │ ├── fetcher/ │ │ └── index.ts # Schema fetching with caching │ ├── parser/ @@ -20,17 +21,24 @@ generator/ │ │ ├── index.ts # Extractor exports │ │ └── synthetic-dto.ts # Inline object type extraction │ ├── generators/ -│ │ ├── index.ts # Generator exports +│ │ ├── index.ts # Generator exports │ │ ├── domain-classifier.ts # Type domain/subdomain mapping │ │ ├── type-mapper.ts # TypeScript → PHP type mapping │ │ ├── type-resolver.ts # Type reference resolution │ │ ├── inheritance-graph.ts # Inheritance tracking │ │ ├── dto.ts # DTO class generation -│ │ ├── enum.ts # Enum class generation +│ │ ├── enum.ts # String literal enum generation +│ │ ├── numeric-enum.ts # TypeScript numeric enum generation +│ │ ├── constants.ts # Protocol constants class generation │ │ ├── union.ts # Union interface generation │ │ ├── factory.ts # Factory class generation │ │ ├── builder.ts # Builder class generation -│ │ └── contract.ts # Contract interface generation +│ │ ├── contract.ts # Contract interface generation +│ │ ├── type-alias-wrapper.ts # Type alias wrapper generation +│ │ ├── intersection-type-wrapper.ts # Intersection type wrapper generation +│ │ ├── schema-map.ts # JSON schema map generation +│ │ ├── skill-generator.ts # Claude Code skill file generation +│ │ └── skill-markdown.ts # Skill markdown helpers │ ├── writers/ │ │ └── index.ts # File writing & base classes │ └── version-tracker/ @@ -58,7 +66,7 @@ generator/ ┌─────────────────────────────────────────────────────────────────┐ │ 2. PARSE SCHEMA │ │ parseSchema() using ts-morph │ -│ - Extracts interfaces, type aliases, enums │ +│ - Extracts interfaces, type aliases, enums, constants │ │ - Handles JSDoc tags (@category, @internal) │ └─────────────────────────────────────────────────────────────────┘ │ @@ -75,11 +83,21 @@ generator/ │ 4. BUILD UNION MEMBERSHIP MAP │ │ - Maps DTOs to their union interfaces │ │ - Detects discriminator fields and values │ +│ - Handles nested unions recursively │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 5. BUILD INHERITANCE GRAPH │ +│ 5. BUILD VERSION TRACKER │ +│ buildVersionTracker() │ +│ - Fetches historical schema versions │ +│ - Tracks when definitions/properties were introduced │ +│ - Annotates @since tags in generated code │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 6. BUILD INHERITANCE GRAPH │ │ buildInheritanceGraph() │ │ - Parent-child relationships from extends │ │ - Topological sort for generation order │ @@ -88,7 +106,7 @@ generator/ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 6. CLASSIFY DOMAINS │ +│ 7. CLASSIFY DOMAINS │ │ DomainClassifier.classify() │ │ - @category tags → domain/subdomain │ │ - Fallback: name-based pattern matching │ @@ -96,19 +114,33 @@ generator/ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 7. GENERATE PHP CODE │ +│ 8. GENERATE PHP CODE │ │ ├── Base Classes (AbstractDataTransferObject, AbstractEnum) │ +│ ├── Constants Class (McpConstants) │ │ ├── DTOs (from interfaces) │ -│ ├── Enums (from string literal unions) │ +│ ├── String Enums (from string literal unions) │ +│ ├── Numeric Enums (from TypeScript enums) │ │ ├── Union Interfaces (from object type unions) │ │ ├── Factories (for discriminated unions) │ +│ ├── Type Alias Wrappers (for union-referenced aliases) │ +│ ├── Intersection Type Wrappers (for A & B types) │ │ ├── Builders (optional, fluent construction) │ │ └── Contracts (marker interfaces) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 8. WRITE FILES │ +│ 9. GENERATE SKILL FILES │ +│ SkillGenerator.generateAll() │ +│ - SKILL.md entry point │ +│ - Domain reference markdown files │ +│ - JSON data files for programmatic access │ +│ - Shell scripts for searching │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 10. WRITE FILES │ │ FileWriter.writeFiles() │ │ - Directory structure by domain/subdomain │ │ - Dry-run mode support │ @@ -117,6 +149,7 @@ generator/ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ OUTPUT: PHP files in src/ │ +│ OUTPUT: Skill files in skill/ │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -137,6 +170,8 @@ Uses ts-morph for TypeScript AST parsing. - `parseSchema()` - Parse TypeScript content - `extractInterfaces()` - Get interface declarations - `extractTypeAliases()` - Get type aliases +- `extractEnums()` - Get TypeScript enum declarations +- `extractConstants()` - Get exported const declarations - `resolveInheritance()` - Flatten inheritance chain ### Extractors (`extractors/`) @@ -155,21 +190,46 @@ Uses ts-morph for TypeScript AST parsing. - `fromArray()` static factory (auto-hydrates nested objects) - `toArray()` serialization (recursive) - Proper inheritance (`extends`) +- Union interface implementation (`implements`) +- Version annotations (`@since`) -**EnumGenerator** - Class-based enums: +**EnumGenerator** - Class-based enums from string literal unions: - Constants for each value - Static factory methods - `values()` method +**NumericEnumGenerator** - Class-based enums from TypeScript numeric enums: +- Integer constants for each value +- `values()`, `names()`, `isValid()`, `nameFor()` methods +- Used for error codes like `ErrorCode` + +**ConstantsGenerator** - Protocol constants class: +- String constants (protocol versions, JSON-RPC version) +- Numeric constants (error codes) +- Helper methods for error code validation + **UnionGenerator** - Marker interfaces: - Interface per union type - DTOs implement their union interfaces +- Detects parent unions for inheritance **FactoryGenerator** - Discriminator-based routing: - Detects discriminator field (`method`, `type`, `kind`, `role`) - Switch statement routing to concrete types +- Handles nested unions recursively - Returns `null` if no discriminator detected +**TypeAliasWrapperGenerator** - Wrapper classes for type aliases: +- Handles cases like `type EmptyResult = Result` +- Creates wrapper that extends base type +- Implements union interfaces referencing the alias + +**IntersectionTypeWrapperGenerator** - Wrapper classes for intersection types: +- Handles cases like `type GetTaskResult = Result & Task` +- Extends one parent (the "base" type) +- Merges properties from other intersected types +- Implements union interfaces referencing the intersection + **BuilderGenerator** - Fluent builders: - `withPropertyName()` setters - `build()` returns DTO @@ -178,6 +238,17 @@ Uses ts-morph for TypeScript AST parsing. - `WithArrayTransformation` - `ResultContract`, `RequestContract`, etc. +**SchemaMapGenerator** - JSON schema map for tooling: +- Complete type registry with relationships +- RPC method mappings +- Factory information +- Domain organization + +**SkillGenerator** - Claude Code skill files: +- Markdown reference documentation +- JSON data files for programmatic access +- Shell scripts for type searching + ### Type Mapping (`generators/type-mapper.ts`) TypeScript to PHP type conversion: @@ -191,6 +262,7 @@ TypeScript to PHP type conversion: | `Type[]` | `array` with PHPDoc | | `Type \| null` | `?Type` | | inline object | Synthetic DTO | +| `typeof CONSTANT` | Resolved constant value | Integer detection patterns: `*Id`, `*Length`, `*Count`, `*Index`, `*Items` @@ -204,6 +276,8 @@ Maps `@category` tags to PHP namespaces: | `Resources` | Server | Resources | | `Sampling` | Client | Sampling | | `JSON-RPC` | Common | JsonRpc | +| `Tasks` | Client | Tasks | +| `Elicitation` | Client | Elicitation | ### Inheritance Graph (`generators/inheritance-graph.ts`) @@ -213,6 +287,15 @@ Manages TypeScript `extends` relationships: - Provides topological sort (parents before children) - Classifies properties as own/inherited/narrowed +### Version Tracker (`version-tracker/index.ts`) + +Tracks schema history for version annotations: + +- Fetches historical schema versions +- Detects when definitions were introduced +- Detects when properties were added +- Provides `@since` annotation data + ### Writers (`writers/index.ts`) File output with organization: @@ -220,3 +303,52 @@ File output with organization: - DTOs: `Domain/Subdomain/ClassName.php` - Others: `Domain/Subdomain/Type/ClassName.php` - Generates base classes (`AbstractDataTransferObject`, `AbstractEnum`, `ValidatesRequiredFields` trait) + +## Generated Output Structure + +``` +src/ +├── Common/ +│ ├── AbstractDataTransferObject.php +│ ├── AbstractEnum.php +│ ├── McpConstants.php # Protocol constants +│ ├── Traits/ +│ │ └── ValidatesRequiredFields.php +│ ├── Contracts/ # Marker interfaces +│ ├── Protocol/ # Core protocol types +│ ├── JsonRpc/ # JSON-RPC message types +│ ├── Content/ # Content block types +│ └── Tasks/ # Shared task types +├── Server/ +│ ├── Tools/ # Tool definitions +│ ├── Resources/ # Resource management +│ ├── Prompts/ # Prompt templates +│ ├── Logging/ # Logging types +│ ├── Lifecycle/ # Server lifecycle +│ └── Core/ # Server core types +└── Client/ + ├── Sampling/ # LLM sampling + ├── Elicitation/ # User input elicitation + ├── Roots/ # Root directory management + ├── Tasks/ # Task execution + └── Lifecycle/ # Client lifecycle + +skill/ +├── SKILL.md # Entry point for Claude Code +├── reference/ +│ ├── overview.md +│ ├── common.md +│ ├── server.md +│ ├── client.md +│ ├── rpc-methods.md +│ └── factories.md +├── data/ +│ ├── schema-index.json # Lightweight discovery index +│ ├── schema-common.json +│ ├── schema-server.json +│ └── schema-client.json +└── scripts/ + ├── search-types.sh + ├── get-type.sh + └── find-rpc.sh +``` diff --git a/generator/docs/configuration.md b/generator/docs/configuration.md index 622ae02..10f828b 100644 --- a/generator/docs/configuration.md +++ b/generator/docs/configuration.md @@ -90,7 +90,7 @@ node dist/cli/index.js configs Fetched schemas are cached in `.cache/schemas/`: -``` +```text .cache/schemas/ └── modelcontextprotocol_modelcontextprotocol_2025-11-25_schema.ts ``` @@ -100,3 +100,53 @@ Clear cache: ```bash node dist/cli/index.js clear-cache ``` + +## Generated Outputs + +The generator always produces: + +| Output | Description | +|--------|-------------| +| **PHP DTOs** | Data transfer objects in `src/` | +| **Constants Class** | `McpConstants.php` with protocol constants and error codes | +| **Union Interfaces** | Marker interfaces for polymorphic types | +| **Factory Classes** | Discriminator-based instantiation | +| **Type Alias Wrappers** | Concrete classes for aliases referenced in unions | +| **Intersection Wrappers** | Concrete classes for intersection types | +| **Contracts** | Marker interfaces for type hierarchies | +| **Skill Files** | Claude Code reference docs in `skill/` | + +## Skill Files + +Skill files are generated automatically to `skill/` (sibling to `src/`): + +```text +skill/ +├── SKILL.md # Entry point for Claude Code +├── reference/ # Markdown documentation +│ ├── overview.md +│ ├── common.md +│ ├── server.md +│ ├── client.md +│ ├── rpc-methods.md +│ └── factories.md +├── data/ # JSON data for programmatic access +│ ├── schema-index.json # Lightweight discovery index (~2KB) +│ ├── schema-common.json +│ ├── schema-server.json +│ └── schema-client.json +└── scripts/ # Search utilities + ├── search-types.sh # Search types by name + ├── get-type.sh # Get type details + └── find-rpc.sh # Find RPC methods +``` + +## Version Tracking + +The generator automatically tracks schema history for `@since` annotations: + +1. Fetches historical schema versions up to the target version +2. Compares definitions and properties across versions +3. Annotates each definition/property with introduction version + +This happens automatically during generation (no configuration needed). diff --git a/generator/docs/design-decisions.md b/generator/docs/design-decisions.md index d9dfa8a..d0ea78c 100644 --- a/generator/docs/design-decisions.md +++ b/generator/docs/design-decisions.md @@ -13,6 +13,7 @@ The generator parses the TypeScript schema directly using ts-morph instead of th - **Inheritance chains** - TypeScript `extends` maps directly to PHP class inheritance; JSON Schema flattens these into `allOf` - **Domain classification** - `@category` JSDoc tags enable accurate domain/subdomain organization - **Union semantics** - TypeScript distinguishes string literal unions (enums) from object type unions (polymorphic interfaces) +- **Constants extraction** - Exported const declarations are preserved with their values **JSON Schema limitations:** @@ -20,6 +21,7 @@ The generator parses the TypeScript schema directly using ts-morph instead of th - No JSDoc metadata (`@category` tags unavailable) - Inline objects become anonymous `$defs` entries - Union types lose semantic context +- Constants are not represented **Trade-off:** Requires ts-morph dependency for AST parsing, but produces more accurate PHP output. @@ -45,11 +47,13 @@ class InitializeRequest extends JSONRPCRequest { ... } ``` **Benefits:** + - Eliminates code duplication - Enables `instanceof` checks - Mirrors TypeScript structure **Implementation details:** + - Properties are `protected` (not `private`) for child access - `fromArray()` returns `static` for late static binding - `toArray()` merges with `parent::toArray()` @@ -116,6 +120,26 @@ class LoggingLevel extends AbstractEnum { } ``` +### TypeScript Enums → Numeric Enum Classes + +```typescript +enum ErrorCode { + ParseError = -32700, + InvalidRequest = -32600, +} +``` + +```php +final class ErrorCode { + public const ParseError = -32700; + public const InvalidRequest = -32600; + + public static function values(): array { ... } + public static function names(): array { ... } + public static function isValid(int $code): bool { ... } +} +``` + ### Object Type Unions → Interfaces + Factories ```typescript @@ -138,6 +162,104 @@ class ContentBlockFactory { } ``` +## Type Alias Wrappers + +Type aliases that are referenced in unions need concrete classes: + +```typescript +// TypeScript +type EmptyResult = Result; +type ServerResult = EmptyResult | InitializeResult | ...; +``` + +**Problem:** `EmptyResult` is just an alias - no class exists. + +**Solution:** Generate wrapper classes: + +```php +class EmptyResult extends Result implements ServerResultInterface, ClientResultInterface +{ + // Inherits everything from Result + // Exists solely for union interface implementation +} +``` + +This enables: + +- Factory routing to `EmptyResult` +- Type-safe union handling +- Documentation accuracy + +## Intersection Type Wrappers + +Intersection types combine properties from multiple interfaces: + +```typescript +// TypeScript +type GetTaskResult = Result & Task; +type ClientResult = EmptyResult | GetTaskResult | ...; +``` + +**Problem:** PHP only supports single inheritance. + +**Solution:** Generate wrapper classes that: + +1. Extend the most "generic" type (typically `Result`) +2. Merge properties from other intersected types +3. Implement all union interfaces + +```php +class GetTaskResult extends Result implements ClientResultInterface, ServerResultInterface +{ + // Inherited from Result: _meta + + // Merged from Task: + protected string $taskId; + protected string $status; + protected string $createdAt; + // ... + + public function __construct(...) { + parent::__construct(...); + $this->taskId = $taskId; + // ... + } +} +``` + +**Base type selection heuristic:** If `Result` is in the intersection, it's always the base type (most generic). Otherwise, use the first type. + +## Protocol Constants + +Exported constants from the schema become a dedicated class: + +```typescript +// TypeScript +export const LATEST_PROTOCOL_VERSION = "2025-11-25"; +export const JSONRPC_VERSION = "2.0"; +export const PARSE_ERROR = -32700; +``` + +```php +final class McpConstants +{ + public const LATEST_PROTOCOL_VERSION = '2025-11-25'; + public const JSONRPC_VERSION = '2.0'; + public const PARSE_ERROR = -32700; + + public static function getErrorCodes(): array { ... } + public static function isValidErrorCode(int $code): bool { ... } + public static function getErrorCodeName(int $code): ?string { ... } + public static function isStandardJsonRpcError(int $code): bool { ... } +} +``` + +**Benefits:** + +- Centralized protocol configuration +- Type-safe error code validation +- Consistent with TypeScript schema + ## Discriminator Detection Factories detect discriminator fields with this priority: @@ -149,6 +271,17 @@ Factories detect discriminator fields with this priority: If no common discriminator exists, no factory is generated. +### Nested Union Handling + +Unions can contain other unions as members: + +```typescript +type ClientResult = InitializeResult | ResourceResult | ...; +type ResourceResult = ReadResourceResult | ListResourcesResult | ...; +``` + +Factories handle this by recursively extracting leaf interfaces to detect discriminators across the entire hierarchy. + ## Auto-Hydration `fromArray()` automatically hydrates nested objects: @@ -190,6 +323,7 @@ Types are organized by MCP domain using `@category` JSDoc tags: | `Prompts` | Server | Prompts | | `Sampling` | Client | Sampling | | `Elicitation` | Client | Elicitation | +| `Tasks` | Client | Tasks | | `JSON-RPC` | Common | JsonRpc | | `Protocol` | Common | Protocol | @@ -233,3 +367,76 @@ class TextContent extends AbstractDataTransferObject ``` Constants enable runtime type inspection without reflection. + +## Version Tracking + +The generator tracks schema history for `@since` annotations: + +```php +/** + * @since 2024-11-05 + * @mcp-version 2025-11-25 + */ +class CallToolRequest extends Request +{ + /** + * @since 2024-11-05 + */ + protected string $name; + + /** + * @since 2025-03-26 + */ + protected ?array $arguments; +} +``` + +**Implementation:** + +1. Fetches all schema versions from GitHub +2. Parses each version to extract definitions +3. Compares versions to detect when each definition/property was introduced +4. Annotates generated code with introduction version + +**Benefits:** + +- Consumers know API stability +- Migration guidance between versions +- Documentation of schema evolution + +## typeof CONSTANT Resolution + +TypeScript uses `typeof CONSTANT_NAME` to reference constant types: + +```typescript +interface JSONRPCRequest { + jsonrpc: typeof JSONRPC_VERSION; // resolves to "2.0" +} +``` + +The generator resolves these to literal types using a constants map built during parsing. + +## Skill File Generation + +The generator produces Claude Code skill files for progressive schema discovery: + +**Why skill files:** + +- Reduce context size for LLM interactions +- Enable progressive discovery (index → domain → type details) +- Provide programmatic access via JSON +- Include search utilities for quick lookups + +**Structure:** + +- `SKILL.md` - Entry point with navigation +- `reference/*.md` - Domain-organized documentation +- `data/*.json` - Programmatic access (index ~2KB, domains ~20KB each) +- `scripts/*.sh` - Search utilities using jq + +**Design principles:** + +- Lightweight index for initial discovery +- Split by domain to limit context loading +- JSON for LLM tool use +- Shell scripts for quick human lookups diff --git a/generator/src/extractors/synthetic-dto.ts b/generator/src/extractors/synthetic-dto.ts index 27555da..2a7c1ae 100644 --- a/generator/src/extractors/synthetic-dto.ts +++ b/generator/src/extractors/synthetic-dto.ts @@ -275,14 +275,41 @@ export class SyntheticDtoExtractor { /** * Parses a single property string like "name?: string" or "$schema?: string". + * Also handles JSDoc comments that may precede the property definition. */ private parsePropertyString(propStr: string): TsProperty | null { if (!propStr) { return null; } + // Extract JSDoc description if present + let description: string | undefined; + let cleanPropStr = propStr; + + // Check for JSDoc comment: /** ... */ + const jsDocMatch = propStr.match(/^\/\*\*[\s\S]*?\*\/\s*/); + if (jsDocMatch) { + const jsDocComment = jsDocMatch[0]; + cleanPropStr = propStr.slice(jsDocComment.length).trim(); + + // Extract the description text from the JSDoc + // Remove /** and */ and clean up the * prefixes on each line + const commentContent = jsDocComment + .replace(/^\/\*\*\s*/, '') // Remove opening /** + .replace(/\*\/\s*$/, '') // Remove closing */ + .split('\n') + .map(line => line.replace(/^\s*\*\s?/, '').trimEnd()) // Remove * prefix and trailing whitespace + .filter(line => line.length > 0) + .join(' ') + .trim(); + + if (commentContent) { + description = commentContent; + } + } + // Match pattern: name?: Type (name can start with $ like $schema) - const match = propStr.match(/^(\$?\w+)(\?)?:\s*(.+)$/); + const match = cleanPropStr.match(/^(\$?\w+)(\?)?:\s*(.+)$/); if (!match) { return null; } @@ -297,6 +324,7 @@ export class SyntheticDtoExtractor { type: typeStr.trim(), isOptional: optional === '?', isReadonly: false, + description, }; } } diff --git a/generator/src/generators/constants.ts b/generator/src/generators/constants.ts new file mode 100644 index 0000000..5ccb53a --- /dev/null +++ b/generator/src/generators/constants.ts @@ -0,0 +1,214 @@ +/** + * MCP PHP Schema Generator - Constants Generator + * + * Generates a PHP class containing all exported constants from the TypeScript schema. + * This includes protocol version strings, JSON-RPC version, and error codes. + */ + +import type { TsConstant, GeneratorConfig } from '../types/index.js'; +import { formatPhpDocDescription } from './index.js'; + +/** + * Escapes a string value for use in a PHP single-quoted string literal. + * + * In PHP single-quoted strings, only single quotes and backslashes need escaping: + * - ' becomes \' + * - \ becomes \\ + */ +function escapePhpSingleQuotedString(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); +} + +/** + * Generates a PHP constants class from TypeScript exported constants. + */ +export class ConstantsGenerator { + private readonly config: GeneratorConfig; + + constructor(config: GeneratorConfig) { + this.config = config; + } + + /** + * Generates the PHP constants class. + */ + generate(constants: readonly TsConstant[]): string { + const indent = this.getIndent(); + const namespace = `${this.config.output.namespace}\\Common`; + + const lines: string[] = []; + + // PHP opening tag + lines.push(' c.valueType === 'string'); + const numericConstants = constants.filter((c) => c.valueType === 'number'); + + // String constants (protocol versions, etc.) + if (stringConstants.length > 0) { + lines.push(`${indent}// Protocol constants`); + for (const constant of stringConstants) { + if (constant.description) { + lines.push(''); + lines.push(`${indent}/**`); + lines.push(...formatPhpDocDescription(constant.description, indent)); + lines.push(`${indent} */`); + } + lines.push(`${indent}public const ${constant.name} = '${escapePhpSingleQuotedString(String(constant.value))}';`); + } + lines.push(''); + } + + // Numeric constants (error codes) + if (numericConstants.length > 0) { + lines.push(`${indent}// Error codes`); + for (const constant of numericConstants) { + if (constant.description) { + lines.push(''); + lines.push(`${indent}/**`); + lines.push(...formatPhpDocDescription(constant.description, indent)); + lines.push(`${indent} */`); + } + lines.push(`${indent}public const ${constant.name} = ${constant.value};`); + } + lines.push(''); + } + + // Helper methods for error codes + if (numericConstants.length > 0) { + this.addErrorCodeHelpers(lines, numericConstants, indent); + } + + // Closing brace + lines.push('}'); + lines.push(''); + + return lines.join('\n'); + } + + /** + * Adds helper methods for error code handling. + */ + private addErrorCodeHelpers( + lines: string[], + errorCodes: readonly TsConstant[], + indent: string + ): void { + // getErrorCodes() method + lines.push(`${indent}/**`); + lines.push(`${indent} * Returns all error codes defined in this class.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @return int[]`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function getErrorCodes(): array`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return [`); + for (const constant of errorCodes) { + lines.push(`${indent}${indent}${indent}self::${constant.name},`); + } + lines.push(`${indent}${indent}];`); + lines.push(`${indent}}`); + lines.push(''); + + // getErrorCodeNames() method + lines.push(`${indent}/**`); + lines.push(`${indent} * Returns error code names mapped to their values.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @return array`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function getErrorCodeNames(): array`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return [`); + for (const constant of errorCodes) { + lines.push(`${indent}${indent}${indent}'${constant.name}' => self::${constant.name},`); + } + lines.push(`${indent}${indent}];`); + lines.push(`${indent}}`); + lines.push(''); + + // isValidErrorCode() method + lines.push(`${indent}/**`); + lines.push(`${indent} * Checks if the given error code is valid.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param int $code`); + lines.push(`${indent} * @return bool`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function isValidErrorCode(int $code): bool`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return in_array($code, self::getErrorCodes(), true);`); + lines.push(`${indent}}`); + lines.push(''); + + // getErrorCodeName() method + lines.push(`${indent}/**`); + lines.push(`${indent} * Gets the constant name for an error code.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param int $code`); + lines.push(`${indent} * @return string|null The constant name, or null if not found`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function getErrorCodeName(int $code): ?string`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}$flipped = array_flip(self::getErrorCodeNames());`); + lines.push(`${indent}${indent}return $flipped[$code] ?? null;`); + lines.push(`${indent}}`); + lines.push(''); + + // isStandardJsonRpcError() method + lines.push(`${indent}/**`); + lines.push(`${indent} * Checks if an error code is a standard JSON-RPC error.`); + lines.push(`${indent} *`); + lines.push(`${indent} * Standard JSON-RPC errors are in the range -32700 to -32600.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param int $code`); + lines.push(`${indent} * @return bool`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function isStandardJsonRpcError(int $code): bool`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return $code >= -32700 && $code <= -32600;`); + lines.push(`${indent}}`); + } + + /** + * Gets the output path for the constants file. + */ + getOutputPath(): string { + return 'Common/McpConstants.php'; + } + + /** + * Gets the indentation string. + */ + private getIndent(): string { + if (this.config.output.indentation === 'tabs') { + return '\t'; + } + return ' '.repeat(this.config.output.indentSize); + } +} diff --git a/generator/src/generators/dto.ts b/generator/src/generators/dto.ts index 1add5fc..6037839 100644 --- a/generator/src/generators/dto.ts +++ b/generator/src/generators/dto.ts @@ -224,12 +224,16 @@ export class DtoGenerator { // Check for const values (discriminator fields) const constValue = this.extractConstValue(p.type); + // Extract maxItems constraint from JSDoc description (e.g., "Must not exceed 100 items") + const maxItems = this.extractMaxItems(p.description); + return { name: p.name, type: phpType, description: p.description, isRequired: !p.isOptional, constValue, + maxItems, } as PhpProperty; } @@ -264,6 +268,27 @@ export class DtoGenerator { return undefined; } + /** + * Extracts maxItems constraint from JSDoc description. + * Looks for patterns like "Must not exceed N items" in the description. + * + * @param description - The property's JSDoc description + * @returns The max items number if found, undefined otherwise + */ + private extractMaxItems(description?: string): number | undefined { + if (!description) { + return undefined; + } + + // Match patterns like "Must not exceed 100 items" (case-insensitive) + const match = description.match(/must not exceed (\d+) items/i); + if (match?.[1]) { + return parseInt(match[1], 10); + } + + return undefined; + } + /** * Gets the PHP namespace for a classification. * DTOs are placed directly in the subdomain namespace (no Dto subfolder). @@ -393,6 +418,14 @@ export class DtoGenerator { lines.push(''); } + // MaxItems constants for array properties with length limits + // Generated from JSDoc patterns like "Must not exceed N items" + const maxItemsConstants = this.renderMaxItemsConstants(meta.properties, indent); + if (maxItemsConstants.length > 0) { + lines.push(...maxItemsConstants); + lines.push(''); + } + // Properties (only own properties, not inherited) if (propertiesToDeclare.length > 0) { lines.push(...this.renderProperties(propertiesToDeclare, indent, interfaceName)); @@ -647,6 +680,79 @@ export class DtoGenerator { return lines; } + /** + * Renders maxItems constants for array properties with length limits. + * + * Generates constants like MAX_VALUES = 100 for properties that have + * maxItems constraints extracted from JSDoc comments. + * + * @param properties - Properties to check for maxItems + * @param indent - Indentation string + * @returns Array of constant declaration lines + */ + private renderMaxItemsConstants(properties: readonly PhpProperty[], indent: string): string[] { + const propsWithMaxItems = properties.filter((p) => p.maxItems !== undefined); + + if (propsWithMaxItems.length === 0) { + return []; + } + + const lines: string[] = []; + for (const prop of propsWithMaxItems) { + // Generate constant name: values -> MAX_VALUES, items -> MAX_ITEMS + const constName = `MAX_${this.toConstantName(prop.name)}`; + lines.push(`${indent}/** Maximum number of items allowed in ${prop.name} per MCP spec */`); + lines.push(`${indent}public const ${constName} = ${prop.maxItems};`); + } + return lines; + } + + /** + * Renders maxItems validation code for fromArray(). + * + * Generates validation that throws InvalidArgumentException when an array + * property exceeds its maxItems limit. + * + * @param properties - Properties with maxItems constraints + * @param indent - Indentation string + * @returns Array of validation code lines + */ + private renderMaxItemsValidation(properties: readonly PhpProperty[], indent: string): string[] { + const lines: string[] = []; + + for (const prop of properties) { + if (prop.maxItems === undefined) continue; + + const { jsonKey } = this.getPropertyNames(prop.name); + const constName = `MAX_${this.toConstantName(prop.name)}`; + + // For required properties, we know the field exists (assertRequired already passed) + // For optional properties, only validate if the field is present + // Use is_array() check to satisfy PHPStan (data values are mixed) + if (prop.isRequired) { + lines.push(`${indent}${indent}if (is_array($data['${jsonKey}']) && count($data['${jsonKey}']) > self::${constName}) {`); + lines.push(`${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf(`); + lines.push(`${indent}${indent}${indent}${indent}'%s::${prop.name} must not exceed %d items, got %d',`); + lines.push(`${indent}${indent}${indent}${indent}static::class,`); + lines.push(`${indent}${indent}${indent}${indent}self::${constName},`); + lines.push(`${indent}${indent}${indent}${indent}count($data['${jsonKey}'])`); + lines.push(`${indent}${indent}${indent}));`); + lines.push(`${indent}${indent}}`); + } else { + lines.push(`${indent}${indent}if (isset($data['${jsonKey}']) && is_array($data['${jsonKey}']) && count($data['${jsonKey}']) > self::${constName}) {`); + lines.push(`${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf(`); + lines.push(`${indent}${indent}${indent}${indent}'%s::${prop.name} must not exceed %d items, got %d',`); + lines.push(`${indent}${indent}${indent}${indent}static::class,`); + lines.push(`${indent}${indent}${indent}${indent}self::${constName},`); + lines.push(`${indent}${indent}${indent}${indent}count($data['${jsonKey}'])`); + lines.push(`${indent}${indent}${indent}));`); + lines.push(`${indent}${indent}}`); + } + } + + return lines; + } + /** * Renders property declarations. * @@ -904,6 +1010,13 @@ export class DtoGenerator { lines.push(''); } + // Validate maxItems constraints for array properties (from JSDoc "Must not exceed N items") + const propsWithMaxItems = sortedProps.filter((p) => p.maxItems !== undefined); + if (propsWithMaxItems.length > 0) { + lines.push(...this.renderMaxItemsValidation(propsWithMaxItems, indent)); + lines.push(''); + } + // Generate constructor call with individual parameters // Each class overrides fromArray(), so `new self()` is correct for each class if (sortedProps.length === 0) { @@ -1158,6 +1271,24 @@ export class DtoGenerator { }; } + // Handle index signatures with string values: { [key: string]: string } + // These use asStringMap() for runtime validation that all values are strings + if (phpType.isIndexSignature && phpType.indexSignatureValueType === 'string') { + return { + expression: `self::asStringMap${suffix}(${varExpr})`, + needsVariable: false, + }; + } + + // Handle index signatures with object values: { [key: string]: object } + // These use asObjectMap() for runtime validation that all values are objects + if (phpType.isIndexSignature && phpType.indexSignatureValueType === 'object') { + return { + expression: `self::asObjectMap${suffix}(${varExpr})`, + needsVariable: false, + }; + } + // Check if it's an array of DTO objects if (phpType.isArray) { const itemType = phpType.arrayItemType ?? ''; @@ -1204,7 +1335,7 @@ export class DtoGenerator { // Array of primitives - use asArray() helper return { - expression: `self::asArray(${varExpr})`, + expression: `self::asArray${suffix}(${varExpr})`, needsVariable: false, }; } @@ -1234,6 +1365,15 @@ export class DtoGenerator { }; } + // Handle string|number union type (ProgressToken pattern) + // This is an untyped PHP 7.4 field with phpDocType 'string|number' + if (phpType.isUntyped && phpType.phpDocType === 'string|number') { + return { + expression: `self::asStringOrNumber${suffix}(${varExpr})`, + needsVariable: false, + }; + } + // Unknown or mixed type - return as-is return { expression: varExpr, @@ -1369,11 +1509,22 @@ export class DtoGenerator { } } + // Array types without a single arrayItemType can still be arrays of DTO unions, e.g.: + // array where Foo and Bar are DTOs. At runtime, allow both DTO objects and already-serialized arrays. + if (phpType.isArray && !phpType.arrayItemType && phpType.phpDocType?.includes('\\WP\\McpSchema\\')) { + return `array_map(static fn($item) => (is_object($item) && method_exists($item, 'toArray')) ? $item->toArray() : $item, ${varExpr})`; + } + // Check if it's a single DTO object (not a primitive) if (!phpType.isArray && !DtoGenerator.PRIMITIVE_TYPES.has(phpType.type)) { return `${varExpr}->toArray()`; } + // Untyped (PHP 7.4) properties can represent DTO unions via PHPDoc. Serialize to arrays when runtime value is a DTO. + if (phpType.isUntyped && phpType.phpDocType?.includes('\\WP\\McpSchema\\')) { + return `(is_object(${varExpr}) && method_exists(${varExpr}, 'toArray')) ? ${varExpr}->toArray() : ${varExpr}`; + } + // Primitive type or array of primitives - return as-is return varExpr; } diff --git a/generator/src/generators/index.ts b/generator/src/generators/index.ts index 76af5ac..47251cc 100644 --- a/generator/src/generators/index.ts +++ b/generator/src/generators/index.ts @@ -6,16 +6,34 @@ export { DtoGenerator } from './dto.js'; export { EnumGenerator } from './enum.js'; +export { NumericEnumGenerator } from './numeric-enum.js'; +export { ConstantsGenerator } from './constants.js'; export { UnionGenerator } from './union.js'; export { FactoryGenerator } from './factory.js'; export { BuilderGenerator } from './builder.js'; export { ContractGenerator } from './contract.js'; +export { TypeAliasWrapperGenerator } from './type-alias-wrapper.js'; +export type { TypeAliasWrapperInfo } from './type-alias-wrapper.js'; +export { IntersectionTypeWrapperGenerator } from './intersection-type-wrapper.js'; +export type { IntersectionTypeWrapperInfo } from './intersection-type-wrapper.js'; export { TypeMapper, createConstantsMap } from './type-mapper.js'; +export { SchemaMapGenerator } from './schema-map.js'; +export type { + SchemaMap, + SchemaMapType, + SchemaMapFactory, + SchemaMapRpc, + SchemaMapDomain, + SchemaMapIndex, + SchemaMapProperty, +} from './schema-map.js'; export type { ConstantsMap } from './type-mapper.js'; export { DomainClassifier } from './domain-classifier.js'; export { TypeResolver } from './type-resolver.js'; export type { ResolvedType } from './type-resolver.js'; export type { ContractInfo, GeneratedContract } from './contract.js'; +export { SkillGenerator } from './skill-generator.js'; +export * from './skill-markdown.js'; // Inheritance graph utilities export { diff --git a/generator/src/generators/intersection-type-wrapper.ts b/generator/src/generators/intersection-type-wrapper.ts new file mode 100644 index 0000000..f2ff234 --- /dev/null +++ b/generator/src/generators/intersection-type-wrapper.ts @@ -0,0 +1,681 @@ +/** + * MCP PHP Schema Generator - Intersection Type Wrapper Generator + * + * Generates PHP wrapper classes for TypeScript intersection types (A & B) + * that are referenced in union types. + * + * ## Problem This Solves + * + * In TypeScript, an intersection type like `type GetTaskResult = Result & Task` + * creates a type that has ALL properties from both Result and Task. When such + * types appear in union types like: + * + * ```typescript + * type ClientResult = EmptyResult | GetTaskResult | CancelTaskResult | ... + * type ServerResult = EmptyResult | GetTaskResult | CancelTaskResult | ... + * ``` + * + * The PHP generator creates interfaces (e.g., `ClientResultInterface`) that + * list `GetTaskResult` as a member. But since `GetTaskResult` is an intersection + * type (not an interface), no PHP class is generated for it, causing: + * + * 1. Documentation listing non-existent types + * 2. No way to type-hint results from `tasks/get` or `tasks/cancel` requests + * 3. Union type implementations being incomplete + * + * ## Solution + * + * This generator creates concrete classes that: + * 1. Inherit from one parent (the "base" type, typically Result) + * 2. Include all properties from the other parent(s) as class properties + * 3. Implement all union interfaces that reference the intersection type + * + * For example, `GetTaskResult = Result & Task` becomes: + * + * ```php + * class GetTaskResult extends Result implements ClientResultInterface, ServerResultInterface + * { + * // Includes all Task properties: taskId, status, createdAt, etc. + * // Inherits _meta from Result + * } + * ``` + * + * ## PHP Limitation: Single Inheritance + * + * PHP only supports single inheritance, so we can't do `extends Result, Task`. + * Instead, we: + * - Extend the most "generic" type (Result in this case) + * - Copy all properties from the other types into the generated class + * - Merge the fromArray/toArray logic to handle all properties + */ + +import type { TsTypeAlias, TsInterface, TsProperty, GeneratorConfig, DomainClassification, UnionMembershipInfo } from '../types/index.js'; +import { DomainClassifier } from './domain-classifier.js'; +import { TypeMapper } from './type-mapper.js'; +import { formatPhpDocDescription } from './index.js'; + +// Note: TypeMapper.mapType is a static method, so we call it directly as TypeMapper.mapType() + +/** + * Information about an intersection type that needs a wrapper class. + */ +export interface IntersectionTypeWrapperInfo { + /** The intersection type name (e.g., 'GetTaskResult') */ + readonly typeName: string; + /** The types being intersected (e.g., ['Result', 'Task']) */ + readonly intersectedTypes: readonly string[]; + /** The primary/base type to extend (e.g., 'Result') */ + readonly baseType: string; + /** Additional types whose properties are merged in */ + readonly mergedTypes: readonly string[]; + /** Union interfaces this type should implement */ + readonly unionInterfaces: readonly UnionMembershipInfo[]; + /** Original type alias for description/tags */ + readonly typeAlias: TsTypeAlias; + /** All properties combined from intersected types */ + readonly allProperties: readonly TsProperty[]; + /** Properties from base type (inherited) */ + readonly baseProperties: readonly TsProperty[]; + /** Properties from merged types (declared in class) */ + readonly ownProperties: readonly TsProperty[]; +} + +/** + * Generates PHP wrapper classes for intersection types referenced in unions. + */ +export class IntersectionTypeWrapperGenerator { + private readonly classifier: DomainClassifier; + private readonly config: GeneratorConfig; + private readonly interfaces: readonly TsInterface[]; + private readonly typeAliases: readonly TsTypeAlias[]; + + constructor( + config: GeneratorConfig, + interfaces: readonly TsInterface[], + typeAliases: readonly TsTypeAlias[] + ) { + this.config = config; + this.classifier = new DomainClassifier(); + this.interfaces = interfaces; + this.typeAliases = typeAliases; + } + + /** + * Finds all intersection types that need wrapper classes. + * + * An intersection type needs a wrapper when: + * 1. It contains `&` (intersection operator) + * 2. All intersected types are interfaces with generated DTOs + * 3. The intersection type is referenced in at least one union type + * + * @param _unionMembershipMap - Map of type names to their union memberships (unused, kept for API consistency) + * @returns Array of intersection types that need wrapper generation + */ + findIntersectionsNeedingWrappers( + _unionMembershipMap: Map + ): IntersectionTypeWrapperInfo[] { + const result: IntersectionTypeWrapperInfo[] = []; + + for (const alias of this.typeAliases) { + // Only look at intersection types (contain &) + if (!alias.type.includes('&')) { + continue; + } + + // Skip if it also contains | (mixed union/intersection - too complex) + if (alias.type.includes('|')) { + continue; + } + + // Extract the intersected type names + const intersectedTypes = alias.type + .split('&') + .map((t) => t.trim()) + .filter((t) => t.length > 0); + + if (intersectedTypes.length < 2) { + continue; + } + + // Verify all intersected types are interfaces + const allInterfacesExist = intersectedTypes.every((typeName) => + this.interfaces.some((i) => i.name === typeName) + ); + if (!allInterfacesExist) { + continue; + } + + // Check if this intersection is referenced in any union + const unionMemberships = this.findUnionMembershipsForType(alias.name); + if (unionMemberships.length === 0) { + continue; + } + + // Determine base type and merged types + // Heuristic: Result is always the base type if present (most generic) + // Otherwise, use the first type + const baseType = intersectedTypes.includes('Result') + ? 'Result' + : intersectedTypes[0]!; + const mergedTypes = intersectedTypes.filter((t) => t !== baseType); + + // Collect all properties from all intersected types + const allProperties = this.collectProperties(intersectedTypes); + const baseProperties = this.collectProperties([baseType]); + const ownProperties = this.collectProperties(mergedTypes); + + result.push({ + typeName: alias.name, + intersectedTypes, + baseType, + mergedTypes, + unionInterfaces: unionMemberships, + typeAlias: alias, + allProperties, + baseProperties, + ownProperties, + }); + } + + return result; + } + + /** + * Collects properties from a list of interface names. + * Resolves type aliases (e.g., TaskStatus -> string literal union) to their underlying types. + */ + private collectProperties(typeNames: readonly string[]): TsProperty[] { + const properties: TsProperty[] = []; + const seenNames = new Set(); + + for (const typeName of typeNames) { + const iface = this.interfaces.find((i) => i.name === typeName); + if (iface) { + for (const prop of iface.properties) { + // Avoid duplicate properties + if (!seenNames.has(prop.name)) { + seenNames.add(prop.name); + // Resolve type alias if the property type references one + // This handles cases like `status: TaskStatus` where TaskStatus is a string literal union + const resolvedType = this.resolveTypeAlias(prop.type); + if (resolvedType !== prop.type) { + properties.push({ + ...prop, + type: resolvedType, + }); + } else { + properties.push(prop); + } + } + } + } + } + + return properties; + } + + /** + * Resolves a type alias to its underlying type if it's a simple alias (not an intersection/union). + * This is needed because TypeMapper doesn't know about type aliases. + * + * For example: TaskStatus -> '"working" | "input_required" | "completed" | "failed" | "cancelled"' + * + * @param typeName - The type name to resolve + * @returns The resolved type (underlying type if alias, otherwise the original) + */ + private resolveTypeAlias(typeName: string): string { + const trimmed = typeName.trim(); + + // Check if this is a known type alias + const typeAlias = this.typeAliases.find((a) => a.name === trimmed); + if (!typeAlias) { + return typeName; + } + + // Only resolve string literal unions (enums in TypeScript) + // These are type aliases like: type TaskStatus = "working" | "cancelled" | ... + // Don't resolve intersection types or complex unions (those are handled elsewhere) + if (typeAlias.type.includes('&')) { + return typeName; // Don't resolve intersection types + } + + // Clean the type by removing inline comments (// ...) from each line + // This handles the MCP schema format: "working" // description | "cancelled" // description + const cleanedType = typeAlias.type + .split('\n') + .map((line) => { + const commentIndex = line.indexOf('//'); + return commentIndex >= 0 ? line.slice(0, commentIndex).trim() : line.trim(); + }) + .join(' ') + .trim(); + + // Check if all members of the union are string literals + const members = cleanedType.split('|').map((m) => m.trim()).filter((m) => m.length > 0); + const allStringLiterals = members.every( + (m) => (m.startsWith('"') && m.endsWith('"')) || (m.startsWith("'") && m.endsWith("'")) + ); + + if (allStringLiterals && members.length > 0) { + // Return a cleaned-up string literal union type + return members.join(' | '); + } + + return typeName; + } + + /** + * Finds all unions that reference a type by name. + */ + private findUnionMembershipsForType(typeName: string): UnionMembershipInfo[] { + const memberships: UnionMembershipInfo[] = []; + + for (const alias of this.typeAliases) { + // Only look at unions + if (!alias.type.includes('|')) { + continue; + } + + // Extract member names from the union + const members = alias.type + .split('|') + .map((m) => m.trim()) + .filter((m) => m.length > 0); + + // Check if our type is a member + if (members.includes(typeName)) { + const classification = this.classifier.classify(alias.name, alias.tags); + memberships.push({ + unionName: alias.name, + namespace: `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}\\Union`, + // Intersection types typically don't have discriminator values + // since they're result types without a `method` field + discriminatorField: undefined, + discriminatorValue: undefined, + }); + } + } + + return memberships; + } + + /** + * Generates PHP wrapper class code for an intersection type. + */ + generate(info: IntersectionTypeWrapperInfo): string { + const classification = this.classifier.classify(info.typeName, info.typeAlias.tags); + const indent = this.getIndent(); + + return this.renderWrapperClass(info, classification, indent); + } + + /** + * Gets the indentation string. + */ + private getIndent(): string { + if (this.config.output.indentation === 'tabs') { + return '\t'; + } + return ' '.repeat(this.config.output.indentSize); + } + + /** + * Renders the PHP wrapper class. + */ + private renderWrapperClass( + info: IntersectionTypeWrapperInfo, + classification: DomainClassification, + indent: string + ): string { + const lines: string[] = []; + const namespace = `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}`; + + // Find the base class's classification to build its namespace + const baseInterface = this.interfaces.find((i) => i.name === info.baseType); + const baseClassification = baseInterface + ? this.classifier.classify(info.baseType, baseInterface.tags) + : classification; + const baseNamespace = `${this.config.output.namespace}\\${baseClassification.domain}\\${baseClassification.subdomain}`; + + // PHP opening tag + lines.push(' `${u.unionName}Interface`).join(', '); + lines.push(`class ${info.typeName} extends ${info.baseType} implements ${implementsList}`); + lines.push('{'); + + // Properties from merged types + if (info.ownProperties.length > 0) { + for (const prop of info.ownProperties) { + const { phpName } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + const nullable = prop.isOptional || phpType.nullable; + + // Property docblock + lines.push(`${indent}/**`); + if (prop.description) { + lines.push(...formatPhpDocDescription(prop.description, indent)); + lines.push(`${indent} *`); + } + lines.push(`${indent} * @since ${this.config.schema.version}`); + lines.push(`${indent} *`); + lines.push(`${indent} * @var ${phpType.phpDocType || phpType.type}${nullable ? '|null' : ''}`); + lines.push(`${indent} */`); + + // Property declaration + const typeHint = phpType.isUntyped ? '' : (nullable ? `?${phpType.type} ` : `${phpType.type} `); + lines.push(`${indent}protected ${typeHint}$${phpName}${nullable ? ' = null' : ''};`); + lines.push(''); + } + } + + // Constructor + lines.push(`${indent}/**`); + // Document all properties in order: base required, merged required, base optional, merged optional + const allRequired = info.allProperties.filter((p) => !p.isOptional); + const allOptional = info.allProperties.filter((p) => p.isOptional); + + for (const prop of allRequired) { + const { phpName } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + lines.push(`${indent} * @param ${phpType.phpDocType || phpType.type} $${phpName} @since ${this.config.schema.version}`); + } + for (const prop of allOptional) { + const { phpName } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + lines.push(`${indent} * @param ${phpType.phpDocType || phpType.type}|null $${phpName} @since ${this.config.schema.version}`); + } + lines.push(`${indent} */`); + + // Constructor signature + const constructorParams: string[] = []; + for (const prop of allRequired) { + const { phpName } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + const typeHint = phpType.isUntyped ? '' : `${phpType.type} `; + constructorParams.push(`${typeHint}$${phpName}`); + } + for (const prop of allOptional) { + const { phpName } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + const typeHint = phpType.isUntyped ? '' : `?${phpType.type} `; + constructorParams.push(`${typeHint}$${phpName} = null`); + } + + lines.push(`${indent}public function __construct(`); + for (let i = 0; i < constructorParams.length; i++) { + const isLast = i === constructorParams.length - 1; + lines.push(`${indent}${indent}${constructorParams[i]}${isLast ? '' : ','}`); + } + lines.push(`${indent}) {`); + + // Call parent constructor with base properties + const baseRequired = info.baseProperties.filter((p) => !p.isOptional); + const baseOptional = info.baseProperties.filter((p) => p.isOptional); + const parentArgs = [...baseRequired, ...baseOptional].map((p) => { + const { phpName } = this.getPropertyNames(p.name); + return `$${phpName}`; + }).join(', '); + if (parentArgs) { + lines.push(`${indent}${indent}parent::__construct(${parentArgs});`); + } else { + lines.push(`${indent}${indent}parent::__construct();`); + } + + // Assign own properties + for (const prop of info.ownProperties) { + const { phpName } = this.getPropertyNames(prop.name); + lines.push(`${indent}${indent}$this->${phpName} = $${phpName};`); + } + + lines.push(`${indent}}`); + lines.push(''); + + // fromArray static factory + lines.push(`${indent}/**`); + lines.push(`${indent} * Creates an instance from an array.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param array $data`); + lines.push(`${indent} * @return self`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function fromArray(array $data): self`); + lines.push(`${indent}{`); + + // Required fields assertion - use JSON key (original name) for array access + const requiredFields = allRequired.map((p) => { + const { jsonKey } = this.getPropertyNames(p.name); + return `'${jsonKey}'`; + }).join(', '); + if (requiredFields) { + lines.push(`${indent}${indent}self::assertRequired($data, [${requiredFields}]);`); + lines.push(''); + } + + // For string literal union types, we need to extract variables with @var annotations + // This is required for PHPStan to understand the type narrowing + const varsWithAnnotations: string[] = []; + const fromArrayArgs: string[] = []; + const allProps = [...allRequired, ...allOptional]; + const allPropsOptionalFlag = [...allRequired.map(() => false), ...allOptional.map(() => true)]; + + for (let i = 0; i < allProps.length; i++) { + const prop = allProps[i]!; + const isOptional = allPropsOptionalFlag[i] ?? false; + const { phpName, jsonKey } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + + // Check if this is a string literal union type (PHPDoc type contains quotes) + const isStringLiteralUnion = phpType.phpDocType && phpType.phpDocType.includes("'"); + + if (isStringLiteralUnion && !isOptional) { + // Need a variable with @var annotation + const varName = `$${phpName}`; + varsWithAnnotations.push( + `${indent}${indent}/** @var ${phpType.phpDocType} ${varName} */`, + `${indent}${indent}${varName} = self::asString($data['${jsonKey}']);`, + '' + ); + fromArrayArgs.push(varName); + } else { + // Normal inline argument + fromArrayArgs.push(this.renderFromArrayArg(prop, isOptional)); + } + } + + // Add variable extractions if any + if (varsWithAnnotations.length > 0) { + lines.push(...varsWithAnnotations); + } + + // Create instance + lines.push(`${indent}${indent}return new self(`); + for (let i = 0; i < fromArrayArgs.length; i++) { + const isLast = i === fromArrayArgs.length - 1; + lines.push(`${indent}${indent}${indent}${fromArrayArgs[i]}${isLast ? '' : ','}`); + } + lines.push(`${indent}${indent});`); + lines.push(`${indent}}`); + lines.push(''); + + // toArray method + lines.push(`${indent}/**`); + lines.push(`${indent} * Converts the instance to an array.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @return array`); + lines.push(`${indent} */`); + lines.push(`${indent}public function toArray(): array`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}$result = parent::toArray();`); + lines.push(''); + + // Add own properties to result + for (const prop of info.ownProperties) { + const { phpName, jsonKey } = this.getPropertyNames(prop.name); + if (prop.isOptional) { + lines.push(`${indent}${indent}if ($this->${phpName} !== null) {`); + lines.push(`${indent}${indent}${indent}$result['${jsonKey}'] = $this->${phpName};`); + lines.push(`${indent}${indent}}`); + } else { + lines.push(`${indent}${indent}$result['${jsonKey}'] = $this->${phpName};`); + } + } + lines.push(''); + lines.push(`${indent}${indent}return $result;`); + lines.push(`${indent}}`); + + // Getters for own properties + for (const prop of info.ownProperties) { + const { phpName } = this.getPropertyNames(prop.name); + lines.push(''); + const phpType = TypeMapper.mapType(prop.type, prop.name); + const nullable = prop.isOptional || phpType.nullable; + const returnType = phpType.isUntyped ? '' : `: ${nullable ? '?' : ''}${phpType.type}`; + + lines.push(`${indent}/**`); + lines.push(`${indent} * @return ${phpType.phpDocType || phpType.type}${nullable ? '|null' : ''}`); + lines.push(`${indent} */`); + lines.push(`${indent}public function get${this.capitalize(phpName)}()${returnType}`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return $this->${phpName};`); + lines.push(`${indent}}`); + } + + lines.push('}'); + lines.push(''); + + return lines.join('\n'); + } + + /** + * Renders an argument for the fromArray factory method. + */ + private renderFromArrayArg(prop: TsProperty, isOptional = false): string { + const { jsonKey } = this.getPropertyNames(prop.name); + const phpType = TypeMapper.mapType(prop.type, prop.name); + const key = isOptional ? `$data['${jsonKey}'] ?? null` : `$data['${jsonKey}']`; + + // Map the conversion helper + if (phpType.type === 'string') { + return isOptional ? `self::asStringOrNull(${key})` : `self::asString(${key})`; + } + if (phpType.type === 'int') { + return isOptional ? `self::asIntOrNull(${key})` : `self::asInt(${key})`; + } + if (phpType.type === 'bool') { + return isOptional ? `self::asBoolOrNull(${key})` : `self::asBool(${key})`; + } + if (phpType.type === 'float') { + return isOptional ? `self::asFloatOrNull(${key})` : `self::asFloat(${key})`; + } + if (phpType.type === 'array') { + return isOptional ? `self::asArrayOrNull(${key})` : `self::asArray(${key})`; + } + + // For custom types, we'd need more sophisticated handling + // For now, just pass through + return key; + } + + /** + * Capitalizes the first letter of a string. + */ + private capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + /** + * Sanitizes a property name for PHP (strips leading $). + * Returns both the PHP property name and the original JSON key. + * + * Property names like `$schema` in TypeScript need special handling: + * - PHP property name: `schema` (no leading $, since PHP prefixes all vars with $) + * - JSON key: `$schema` (preserve original for serialization) + */ + private getPropertyNames(originalName: string): { phpName: string; jsonKey: string } { + // If name starts with $, strip it for PHP but keep it for JSON serialization + if (originalName.startsWith('$')) { + return { + phpName: originalName.slice(1), + jsonKey: originalName, + }; + } + return { + phpName: originalName, + jsonKey: originalName, + }; + } + + /** + * Gets a human-readable method name from a type name. + * E.g., 'GetTaskResult' -> 'get task' + */ + private getMethodNameFromTypeName(typeName: string): string { + // Remove 'Result' suffix + const withoutResult = typeName.replace(/Result$/, ''); + // Split on capital letters + const words = withoutResult.split(/(?=[A-Z])/); + return words.join(' ').toLowerCase(); + } + + /** + * Gets the output path for a wrapper class. + */ + getOutputPath(info: IntersectionTypeWrapperInfo): string { + const classification = this.classifier.classify(info.typeName, info.typeAlias.tags); + return `${classification.domain}/${classification.subdomain}/${info.typeName}.php`; + } +} diff --git a/generator/src/generators/numeric-enum.ts b/generator/src/generators/numeric-enum.ts new file mode 100644 index 0000000..e90870d --- /dev/null +++ b/generator/src/generators/numeric-enum.ts @@ -0,0 +1,173 @@ +/** + * MCP PHP Schema Generator - Numeric Enum Generator + * + * Generates PHP classes with integer constants from TypeScript numeric enums. + * These are simpler than string literal union enums - just constant definitions. + */ + +import type { TsEnum, GeneratorConfig, DomainClassification } from '../types/index.js'; +import { DomainClassifier } from './domain-classifier.js'; +import { formatPhpDocDescription } from './index.js'; + +/** + * Generates PHP constant classes from TypeScript numeric enums. + */ +export class NumericEnumGenerator { + private readonly classifier: DomainClassifier; + private readonly config: GeneratorConfig; + + constructor(config: GeneratorConfig) { + this.config = config; + this.classifier = new DomainClassifier(); + } + + /** + * Checks if all members have numeric values. + */ + isNumericEnum(tsEnum: TsEnum): boolean { + return tsEnum.members.every((m) => typeof m.value === 'number'); + } + + /** + * Generates PHP code for a numeric enum. + */ + generate(tsEnum: TsEnum): string { + const classification = this.classifier.classify(tsEnum.name, tsEnum.tags); + const indent = this.getIndent(); + + return this.renderNumericEnum(tsEnum, classification, indent); + } + + /** + * Gets the domain classification for output path. + */ + classify(tsEnum: TsEnum): DomainClassification { + return this.classifier.classify(tsEnum.name, tsEnum.tags); + } + + /** + * Gets the indentation string. + */ + private getIndent(): string { + if (this.config.output.indentation === 'tabs') { + return '\t'; + } + return ' '.repeat(this.config.output.indentSize); + } + + /** + * Renders the complete PHP constants class. + */ + private renderNumericEnum( + tsEnum: TsEnum, + classification: DomainClassification, + indent: string + ): string { + const lines: string[] = []; + const namespace = this.getNamespace(classification); + + // PHP opening tag + lines.push('`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function names(): array`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return [`); + for (const member of tsEnum.members) { + lines.push(`${indent}${indent}${indent}'${member.name}' => self::${member.name},`); + } + lines.push(`${indent}${indent}];`); + lines.push(`${indent}}`); + lines.push(''); + + // isValid() static method to check if a code is valid + lines.push(`${indent}/**`); + lines.push(`${indent} * Checks if the given error code is valid.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param int $code`); + lines.push(`${indent} * @return bool`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function isValid(int $code): bool`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}return in_array($code, self::values(), true);`); + lines.push(`${indent}}`); + lines.push(''); + + // nameFor() static method to get constant name for a code + lines.push(`${indent}/**`); + lines.push(`${indent} * Gets the constant name for an error code.`); + lines.push(`${indent} *`); + lines.push(`${indent} * @param int $code`); + lines.push(`${indent} * @return string|null The constant name, or null if not found`); + lines.push(`${indent} */`); + lines.push(`${indent}public static function nameFor(int $code): ?string`); + lines.push(`${indent}{`); + lines.push(`${indent}${indent}$flipped = array_flip(self::names());`); + lines.push(`${indent}${indent}return $flipped[$code] ?? null;`); + lines.push(`${indent}}`); + + // Closing brace + lines.push('}'); + lines.push(''); + + return lines.join('\n'); + } + + /** + * Gets the PHP namespace for a classification. + */ + private getNamespace(classification: DomainClassification): string { + return `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}\\Enum`; + } +} diff --git a/generator/src/generators/schema-map.ts b/generator/src/generators/schema-map.ts new file mode 100644 index 0000000..a5784b4 --- /dev/null +++ b/generator/src/generators/schema-map.ts @@ -0,0 +1,808 @@ +/** + * MCP PHP Schema Generator - Schema Map Generator + * + * Generates a JSON map of all types, their relationships, and connections. + * Designed to help LLMs understand and navigate the generated schema. + */ + +import type { + TsInterface, + TsTypeAlias, + TsEnum, + GeneratorConfig, + DomainClassification, + UnionMembershipMap, +} from '../types/index.js'; +import { DomainClassifier } from './domain-classifier.js'; +import type { IntersectionTypeWrapperInfo } from './intersection-type-wrapper.js'; + +// ============================================================================ +// Schema Map Types +// ============================================================================ + +/** + * Property information for the schema map. + */ +export interface SchemaMapProperty { + readonly name: string; + readonly type: string; + readonly optional: boolean; + readonly description?: string; +} + +/** + * Type information for the schema map. + */ +export interface SchemaMapType { + readonly kind: 'class' | 'enum' | 'union' | 'factory' | 'constant'; + readonly domain: string; + readonly subdomain: string; + readonly namespace: string; + readonly purpose: string; + readonly extends?: string; + readonly implements: string[]; + readonly properties: Record; + readonly usedBy: string[]; + readonly uses: string[]; + readonly discriminator?: { + readonly field: string; + readonly value?: string; + }; +} + +/** + * Factory information for the schema map. + */ +export interface SchemaMapFactory { + readonly interface: string; + readonly discriminator: string; + readonly mappings: Record; + readonly domain: string; + readonly subdomain: string; +} + +/** + * RPC endpoint information. + */ +export interface SchemaMapRpc { + readonly direction: 'client→server' | 'server→client' | 'bidirectional'; + readonly request: string; + readonly params?: string; + readonly result: string; +} + +/** + * Domain summary information. + */ +export interface SchemaMapDomain { + readonly description: string; + readonly types: string[]; + readonly entryPoints: string[]; +} + +/** + * Index for quick lookups. + */ +export interface SchemaMapIndex { + readonly byDomain: Record; + readonly byKind: Record; + readonly byMethod: Record; +} + +/** + * Complete schema map structure. + */ +export interface SchemaMap { + readonly version: string; + readonly schemaUrl: string; + readonly generated: string; + readonly namespace: string; + readonly index: SchemaMapIndex; + readonly types: Record; + readonly factories: Record; + readonly rpc: Record; + readonly domains: Record; +} + +// ============================================================================ +// Schema Map Generator +// ============================================================================ + +/** + * Generates a comprehensive JSON map of the schema for LLM consumption. + */ +export class SchemaMapGenerator { + private readonly config: GeneratorConfig; + private readonly classifier: DomainClassifier; + private readonly interfaces: readonly TsInterface[]; + private readonly typeAliases: readonly TsTypeAlias[]; + private readonly enums: readonly TsEnum[]; + private readonly unionMembershipMap: UnionMembershipMap; + private readonly intersectionTypes: readonly IntersectionTypeWrapperInfo[]; + + // Tracking maps built during generation + private readonly typeUsedBy = new Map>(); + private readonly typeUses = new Map>(); + + constructor( + config: GeneratorConfig, + interfaces: readonly TsInterface[], + typeAliases: readonly TsTypeAlias[], + enums: readonly TsEnum[], + unionMembershipMap: UnionMembershipMap, + classifier?: DomainClassifier, + intersectionTypes?: readonly IntersectionTypeWrapperInfo[] + ) { + this.config = config; + this.classifier = classifier ?? new DomainClassifier(); + this.interfaces = interfaces; + this.typeAliases = typeAliases; + this.enums = enums; + this.unionMembershipMap = unionMembershipMap; + this.intersectionTypes = intersectionTypes ?? []; + } + + /** + * Generates the complete schema map. + */ + generate(): SchemaMap { + // First pass: build all types and track relationships + const types = this.buildTypes(); + + // Second pass: build indexes + const index = this.buildIndex(types); + + // Build factories from union type aliases + const factories = this.buildFactories(); + + // Build RPC mappings + const rpc = this.buildRpcMappings(); + + // Build domain summaries + const domains = this.buildDomains(types); + + // Build schema URL - full path to the schema.ts file for the specific version + const schemaUrl = `https://github.com/${this.config.schema.repository}/blob/${this.config.schema.branch}/${this.config.schema.path}/${this.config.schema.version}/schema.ts`; + + return { + version: this.config.schema.version, + schemaUrl, + generated: new Date().toISOString(), + namespace: this.config.output.namespace, + index, + types, + factories, + rpc, + domains, + }; + } + + /** + * Generates the schema map as a JSON string. + */ + generateJson(pretty = true): string { + const map = this.generate(); + return JSON.stringify(map, null, pretty ? 2 : undefined); + } + + /** + * Gets the output path for the schema map file. + */ + getOutputPath(): string { + return 'schema-map.json'; + } + + // ============================================================================ + // Private Methods - Type Building + // ============================================================================ + + private buildTypes(): Record { + const types: Record = {}; + + // Process interfaces (DTOs) + for (const iface of this.interfaces) { + const classification = this.classifier.classify(iface.name, iface.tags, iface.syntheticParent); + const uses = this.extractTypesFromInterface(iface); + + // Track reverse relationships + for (const usedType of uses) { + this.addUsedBy(usedType, iface.name); + } + this.setUses(iface.name, uses); + + // Get union implementations + const unionMemberships = this.unionMembershipMap.get(iface.name) ?? []; + const implements_ = unionMemberships.map((m) => `${m.unionName}Interface`); + + // Extract discriminator info if available + let discriminator: SchemaMapType['discriminator']; + if (unionMemberships.length > 0) { + const membership = unionMemberships[0]; + if (membership?.discriminatorField && membership?.discriminatorValue) { + discriminator = { + field: membership.discriminatorField, + value: membership.discriminatorValue, + }; + } + } + + types[iface.name] = { + kind: 'class', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: this.getNamespace(classification, iface.name), + purpose: this.generatePurpose(iface), + extends: iface.extends[0], + implements: implements_, + properties: this.extractProperties(iface), + usedBy: [], // Will be populated after all types are processed + uses: Array.from(uses), + discriminator, + }; + } + + // Process enums + for (const enumDef of this.enums) { + const classification = this.classifier.classify(enumDef.name, enumDef.tags); + + types[enumDef.name] = { + kind: 'enum', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: this.getNamespace(classification, enumDef.name, 'Enum'), + purpose: enumDef.description ?? `Enumeration of ${enumDef.name} values`, + implements: [], + properties: Object.fromEntries( + enumDef.members.map((m) => [m.name, String(m.value)]) + ), + usedBy: [], + uses: [], + }; + } + + // Process string literal union enums from type aliases + for (const alias of this.typeAliases) { + if (this.isStringLiteralUnion(alias)) { + const classification = this.classifier.classify(alias.name, alias.tags); + const values = this.extractStringLiteralValues(alias.type); + + types[alias.name] = { + kind: 'enum', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: this.getNamespace(classification, alias.name, 'Enum'), + purpose: alias.description ?? `Enumeration: ${values.join(', ')}`, + implements: [], + properties: Object.fromEntries(values.map((v) => [this.toEnumCase(v), v])), + usedBy: [], + uses: [], + }; + } else if (this.isUnionType(alias)) { + // Union type - creates interface + factory + const classification = this.classifier.classify(alias.name, alias.tags); + const members = this.extractUnionMembers(alias.type); + + // Track that union members are used by this union + for (const member of members) { + this.addUsedBy(member, `${alias.name}Interface`); + } + + types[`${alias.name}Interface`] = { + kind: 'union', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}\\Union\\${alias.name}Interface`, + purpose: alias.description ?? `Union type: ${members.join(' | ')}`, + implements: [], + properties: {}, + usedBy: [], + uses: members, + }; + } else if (this.isSimpleTypeAlias(alias)) { + // Simple type alias like `type EmptyResult = Result` + const classification = this.classifier.classify(alias.name, alias.tags); + const baseType = alias.type.trim(); + + // Track relationship + this.addUsedBy(baseType, alias.name); + + // Get union memberships for this type + const unionMemberships = this.findUnionMembershipsForType(alias.name); + const implements_ = unionMemberships.map((m) => `${m}Interface`); + + types[alias.name] = { + kind: 'class', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: this.getNamespace(classification, alias.name), + purpose: alias.description ?? `Type alias for ${baseType}`, + extends: baseType, + implements: implements_, + properties: {}, + usedBy: [], + uses: [baseType], + }; + } + } + + // Process intersection types (e.g., GetTaskResult = Result & Task) + for (const intersection of this.intersectionTypes) { + const classification = this.classifier.classify(intersection.typeName, intersection.typeAlias.tags); + + // Get union memberships for this type + const implements_ = intersection.unionInterfaces.map((u) => `${u.unionName}Interface`); + + // Build properties from own properties (not inherited from base) + const properties: Record = {}; + for (const prop of intersection.ownProperties) { + properties[prop.name] = this.simplifyType(prop.type) + (prop.isOptional ? '?' : ''); + } + + // Track relationships + for (const usedType of intersection.intersectedTypes) { + this.addUsedBy(usedType, intersection.typeName); + } + + types[intersection.typeName] = { + kind: 'class', + domain: classification.domain, + subdomain: classification.subdomain, + namespace: this.getNamespace(classification, intersection.typeName), + purpose: intersection.typeAlias.description ?? `Intersection type: ${intersection.intersectedTypes.join(' & ')}`, + extends: intersection.baseType, + implements: implements_, + properties, + usedBy: [], + uses: [...intersection.intersectedTypes], + }; + } + + // Populate usedBy from tracked relationships + for (const [typeName, usedBySet] of this.typeUsedBy) { + if (types[typeName]) { + (types[typeName] as { usedBy: string[] }).usedBy = Array.from(usedBySet); + } + } + + return types; + } + + private buildIndex(types: Record): SchemaMapIndex { + const byDomain: Record = {}; + const byKind: Record = {}; + const byMethod: Record = {}; + + for (const [name, type] of Object.entries(types)) { + // Index by domain + const domainKey = `${type.domain}/${type.subdomain}`; + if (!byDomain[domainKey]) { + byDomain[domainKey] = []; + } + byDomain[domainKey].push(name); + + // Index by kind + const kindKey = type.kind; + if (!byKind[kindKey]) { + byKind[kindKey] = []; + } + byKind[kindKey]!.push(name); + + // Index by method (for requests) + if (name.endsWith('Request') && type.discriminator?.value) { + const method = type.discriminator.value; + const resultName = name.replace('Request', 'Result'); + byMethod[method] = { + request: name, + result: types[resultName] ? resultName : 'Result', + }; + } + } + + return { byDomain, byKind, byMethod }; + } + + private buildFactories(): Record { + const factories: Record = {}; + + for (const alias of this.typeAliases) { + if (!this.isUnionType(alias) || this.isStringLiteralUnion(alias)) { + continue; + } + + const members = this.extractUnionMembers(alias.type); + const classification = this.classifier.classify(alias.name, alias.tags); + + // Detect discriminator field + const discriminator = this.detectDiscriminatorField(members); + if (!discriminator) { + continue; + } + + // Build mappings + const mappings: Record = {}; + for (const member of members) { + const iface = this.interfaces.find((i) => i.name === member); + if (iface) { + const prop = iface.properties.find((p) => p.name === discriminator); + if (prop) { + const value = this.extractConstValue(prop.type); + if (value) { + mappings[value] = member; + } + } + } + } + + if (Object.keys(mappings).length > 0) { + factories[`${alias.name}Factory`] = { + interface: `${alias.name}Interface`, + discriminator, + mappings, + domain: classification.domain, + subdomain: classification.subdomain, + }; + } + } + + return factories; + } + + private buildRpcMappings(): Record { + const rpc: Record = {}; + + // Find all request interfaces with method discriminators + for (const iface of this.interfaces) { + if (!iface.name.endsWith('Request')) { + continue; + } + + const methodProp = iface.properties.find((p) => p.name === 'method'); + if (!methodProp) { + continue; + } + + const method = this.extractConstValue(methodProp.type); + if (!method) { + continue; + } + + // Find corresponding result + const resultName = iface.name.replace('Request', 'Result'); + const hasResult = this.interfaces.some((i) => i.name === resultName); + + // Find params + const paramsName = `${iface.name}Params`; + const hasParams = this.interfaces.some((i) => i.name === paramsName); + + // Determine direction based on union memberships + const memberships = this.unionMembershipMap.get(iface.name) ?? []; + const isClientRequest = memberships.some((m) => m.unionName === 'ClientRequest'); + const isServerRequest = memberships.some((m) => m.unionName === 'ServerRequest'); + + let direction: SchemaMapRpc['direction']; + if (isClientRequest && isServerRequest) { + direction = 'bidirectional'; + } else if (isClientRequest) { + direction = 'client→server'; + } else if (isServerRequest) { + direction = 'server→client'; + } else { + direction = 'client→server'; // Default + } + + rpc[method] = { + direction, + request: iface.name, + params: hasParams ? paramsName : undefined, + result: hasResult ? resultName : 'Result', + }; + } + + return rpc; + } + + private buildDomains(types: Record): Record { + const domains: Record = {}; + + const domainDescriptions: Record = { + 'Server/Tools': 'Tool definitions and invocation for server capabilities', + 'Server/Resources': 'Resource listing, reading, and subscription', + 'Server/Prompts': 'Prompt template definitions and retrieval', + 'Server/Logging': 'Server-side logging configuration and messages', + 'Server/Lifecycle': 'Server capabilities and lifecycle management', + 'Server/Core': 'Core server functionality (completion, etc.)', + 'Client/Sampling': 'LLM sampling requests from server to client', + 'Client/Elicitation': 'User input elicitation for gathering information', + 'Client/Roots': 'File system root management', + 'Client/Tasks': 'Async task execution and tracking', + 'Client/Lifecycle': 'Client capabilities and lifecycle management', + 'Common/Protocol': 'Shared protocol types (initialization, progress, etc.)', + 'Common/JsonRpc': 'JSON-RPC base types and messages', + 'Common/Content': 'Content block types (text, image, audio, etc.)', + 'Common/Tasks': 'Shared task-related types', + 'Common/Lifecycle': 'Shared lifecycle types (Implementation)', + 'Common/Core': 'Core shared types (Icon, etc.)', + }; + + // Group types by domain + const domainTypes: Record = {}; + for (const [name, type] of Object.entries(types)) { + const key = `${type.domain}/${type.subdomain}`; + if (!domainTypes[key]) { + domainTypes[key] = []; + } + domainTypes[key].push(name); + } + + // Build domain entries + for (const [key, typeNames] of Object.entries(domainTypes)) { + // Find entry points (requests in this domain) + const entryPoints = typeNames + .filter((name) => name.endsWith('Request') && types[name]?.discriminator?.value) + .map((name) => types[name]?.discriminator?.value ?? name); + + domains[key] = { + description: domainDescriptions[key] ?? `${key} domain types`, + types: typeNames, + entryPoints, + }; + } + + return domains; + } + + // ============================================================================ + // Private Methods - Helpers + // ============================================================================ + + private extractTypesFromInterface(iface: TsInterface): Set { + const types = new Set(); + + // Add parent types + for (const ext of iface.extends) { + types.add(ext); + } + + // Extract types from properties + for (const prop of iface.properties) { + const extracted = this.extractTypeReferences(prop.type); + for (const t of extracted) { + types.add(t); + } + } + + return types; + } + + private extractTypeReferences(type: string): string[] { + const types: string[] = []; + + // Remove array syntax and extract base type + const cleaned = type.replace(/\[\]$/, '').replace(/^readonly\s+/, ''); + + // Skip primitive types + const primitives = ['string', 'number', 'boolean', 'null', 'undefined', 'void', 'never', 'unknown', 'any', 'object']; + if (primitives.includes(cleaned.toLowerCase())) { + return types; + } + + // Handle union types + if (cleaned.includes('|')) { + const parts = cleaned.split('|').map((p) => p.trim()); + for (const part of parts) { + types.push(...this.extractTypeReferences(part)); + } + return types; + } + + // Handle generic types like Array + const genericMatch = cleaned.match(/^(\w+)<(.+)>$/); + if (genericMatch) { + const [, , inner] = genericMatch; + if (inner) { + types.push(...this.extractTypeReferences(inner)); + } + return types; + } + + // Skip inline object types + if (cleaned.startsWith('{')) { + return types; + } + + // Skip string literals + if (cleaned.startsWith('"') || cleaned.startsWith("'")) { + return types; + } + + // This looks like a type reference + if (/^[A-Z]/.test(cleaned)) { + types.push(cleaned); + } + + return types; + } + + private extractProperties(iface: TsInterface): Record { + const props: Record = {}; + + for (const prop of iface.properties) { + props[prop.name] = this.simplifyType(prop.type) + (prop.isOptional ? '?' : ''); + } + + return props; + } + + private simplifyType(type: string): string { + // Remove readonly + let simplified = type.replace(/^readonly\s+/, ''); + + // Simplify array syntax + simplified = simplified.replace(/Array<(.+)>/, '$1[]'); + + // Truncate very long types + if (simplified.length > 50) { + simplified = simplified.substring(0, 47) + '...'; + } + + return simplified; + } + + private generatePurpose(iface: TsInterface): string { + if (iface.description) { + // Take first sentence + const firstSentence = iface.description.split(/[.\n]/)[0]; + return firstSentence?.trim() ?? iface.description.substring(0, 100); + } + + // Generate from name + const name = iface.name; + + if (name.endsWith('Request')) { + const method = iface.properties.find((p) => p.name === 'method'); + if (method) { + const value = this.extractConstValue(method.type); + if (value) { + return `Request for ${value} operation`; + } + } + return `Request for ${name.replace('Request', '')} operation`; + } + + if (name.endsWith('Result')) { + return `Result from ${name.replace('Result', '')} operation`; + } + + if (name.endsWith('Notification')) { + return `Notification for ${name.replace('Notification', '').replace(/([A-Z])/g, ' $1').trim().toLowerCase()} events`; + } + + if (name.endsWith('Params')) { + return `Parameters for ${name.replace('Params', '')}`; + } + + return `${name.replace(/([A-Z])/g, ' $1').trim()} data structure`; + } + + private getNamespace(classification: DomainClassification, name: string, subdir?: string): string { + const parts: string[] = [this.config.output.namespace, classification.domain, classification.subdomain]; + if (subdir) { + parts.push(subdir); + } + parts.push(name); + return parts.join('\\'); + } + + private isStringLiteralUnion(alias: TsTypeAlias): boolean { + // Check if it's a union of string literals like "a" | "b" | "c" + const parts = alias.type.split('|').map((p) => p.trim()); + return parts.every((p) => /^["']/.test(p)); + } + + private isUnionType(alias: TsTypeAlias): boolean { + return alias.type.includes('|'); + } + + private isSimpleTypeAlias(alias: TsTypeAlias): boolean { + // Simple type alias: no union (|) or intersection (&) + // Just a single type reference like `type EmptyResult = Result` + const type = alias.type.trim(); + return ( + !type.includes('|') && + !type.includes('&') && + /^[A-Z][a-zA-Z0-9]*$/.test(type) // Single PascalCase type name + ); + } + + private findUnionMembershipsForType(typeName: string): string[] { + const memberships: string[] = []; + + for (const alias of this.typeAliases) { + // Only look at unions + if (!alias.type.includes('|')) { + continue; + } + + // Extract member names from the union + const members = alias.type + .split('|') + .map((m) => m.trim()) + .filter((m) => m.length > 0); + + // Check if our type is a member + if (members.includes(typeName)) { + memberships.push(alias.name); + } + } + + return memberships; + } + + private extractStringLiteralValues(type: string): string[] { + return type + .split('|') + .map((p) => p.trim()) + .filter((p) => /^["']/.test(p)) + .map((p) => p.replace(/^["']|["']$/g, '')); + } + + private extractUnionMembers(type: string): string[] { + return type + .split('|') + .map((p) => p.trim()) + .filter((p) => /^[A-Z]/.test(p)); + } + + private toEnumCase(value: string): string { + // Convert string like "tools/call" to "TOOLS_CALL" + return value.toUpperCase().replace(/[^A-Z0-9]/g, '_'); + } + + private detectDiscriminatorField(memberNames: string[]): string | undefined { + const memberInterfaces = memberNames + .map((name) => this.interfaces.find((i) => i.name === name)) + .filter((i): i is TsInterface => i !== undefined); + + if (memberInterfaces.length === 0) { + return undefined; + } + + const first = memberInterfaces[0]; + if (!first) { + return undefined; + } + + const commonFields = first.properties + .map((p) => p.name) + .filter((name) => + memberInterfaces.every((m) => m.properties.some((p) => p.name === name)) + ); + + const priorityFields = ['method', 'type', 'kind', 'role']; + return priorityFields.find((f) => commonFields.includes(f)) ?? commonFields[0]; + } + + private extractConstValue(type: string): string | undefined { + const trimmed = type.trim(); + + if (trimmed.startsWith('"') && trimmed.endsWith('"')) { + return trimmed.slice(1, -1); + } + if (trimmed.startsWith("'") && trimmed.endsWith("'")) { + return trimmed.slice(1, -1); + } + + return undefined; + } + + private addUsedBy(typeName: string, usedByType: string): void { + if (!this.typeUsedBy.has(typeName)) { + this.typeUsedBy.set(typeName, new Set()); + } + this.typeUsedBy.get(typeName)?.add(usedByType); + } + + private setUses(typeName: string, uses: Set): void { + this.typeUses.set(typeName, uses); + } +} diff --git a/generator/src/generators/skill-generator.ts b/generator/src/generators/skill-generator.ts new file mode 100644 index 0000000..76b37ee --- /dev/null +++ b/generator/src/generators/skill-generator.ts @@ -0,0 +1,728 @@ +/** + * MCP PHP Schema Generator - Skill Generator + * + * Generates Claude Code skill files for progressive schema discovery. + * Produces markdown references, JSON data files, and search scripts. + */ + +import type { + TsInterface, + TsTypeAlias, + TsEnum, + GeneratorConfig, + UnionMembershipMap, + McpDomain, +} from '../types/index.js'; +import type { + SkillGeneratedFile, + SkillGenerationResult, + SkillSchemaIndex, + SkillDomainData, + SkillTypeInfo, + SkillFactoryInfo, + SkillTypeTableEntry, + SkillSubdomainSection, + SkillRpcEntry, + SkillFactoryEntry, +} from '../types/skill-types.js'; +import type { DomainClassifier } from './domain-classifier.js'; +import type { IntersectionTypeWrapperInfo } from './intersection-type-wrapper.js'; +import type { SchemaMap } from './schema-map.js'; +import { SchemaMapGenerator } from './schema-map.js'; +import { + generateRpcTable, + generateSubdomainSection, + generateFactorySection, + generateTableOfContents, + generateDomainOverviewTable, + generateFrontmatter, + formatKeyProperties, + extractFirstSentence, +} from './skill-markdown.js'; + +// ============================================================================ +// Skill Generator +// ============================================================================ + +/** + * Generates Claude Code skill files from schema data. + */ +export class SkillGenerator { + private readonly config: GeneratorConfig; + private readonly interfaces: readonly TsInterface[]; + private readonly typeAliases: readonly TsTypeAlias[]; + private readonly enums: readonly TsEnum[]; + private readonly unionMembershipMap: UnionMembershipMap; + private readonly classifier: DomainClassifier; + private readonly intersectionTypes: readonly IntersectionTypeWrapperInfo[]; + private readonly outputDir: string; + + // Cached schema map for reuse + private schemaMap: SchemaMap | null = null; + + constructor( + config: GeneratorConfig, + interfaces: readonly TsInterface[], + typeAliases: readonly TsTypeAlias[], + enums: readonly TsEnum[], + unionMembershipMap: UnionMembershipMap, + classifier: DomainClassifier, + intersectionTypes: readonly IntersectionTypeWrapperInfo[], + outputDir = 'skill' + ) { + this.config = config; + this.interfaces = interfaces; + this.typeAliases = typeAliases; + this.enums = enums; + this.unionMembershipMap = unionMembershipMap; + this.classifier = classifier; + this.intersectionTypes = intersectionTypes; + this.outputDir = outputDir; + } + + /** + * Generates all skill files. + */ + generateAll(): SkillGenerationResult { + const files: SkillGeneratedFile[] = []; + + // Ensure schema map is generated + const schemaMap = this.getSchemaMap(); + + // Generate SKILL.md (main entry point) + files.push(this.generateSkillMd(schemaMap)); + + // Generate reference markdown files + files.push(this.generateOverviewMd(schemaMap)); + files.push(this.generateDomainMd('Common', schemaMap)); + files.push(this.generateDomainMd('Server', schemaMap)); + files.push(this.generateDomainMd('Client', schemaMap)); + files.push(this.generateRpcMethodsMd(schemaMap)); + files.push(this.generateFactoriesMd(schemaMap)); + + // Generate JSON data files + files.push(this.generateSchemaIndex(schemaMap)); + files.push(this.generateDomainJson('Common', schemaMap)); + files.push(this.generateDomainJson('Server', schemaMap)); + files.push(this.generateDomainJson('Client', schemaMap)); + + // Generate search scripts + files.push(this.generateSearchTypesScript()); + files.push(this.generateGetTypeScript()); + files.push(this.generateFindRpcScript()); + + // Calculate stats + let totalSize = 0; + let indexSize = 0; + for (const file of files) { + totalSize += file.content.length; + if (file.path.endsWith('schema-index.json')) { + indexSize = file.content.length; + } + } + + return { + files, + stats: { + markdownFiles: files.filter((f) => f.type === 'markdown').length, + jsonFiles: files.filter((f) => f.type === 'json').length, + scriptFiles: files.filter((f) => f.type === 'script').length, + totalSize, + indexSize, + }, + }; + } + + /** + * Gets or creates the schema map. + */ + private getSchemaMap(): SchemaMap { + if (!this.schemaMap) { + const generator = new SchemaMapGenerator( + this.config, + this.interfaces, + this.typeAliases, + this.enums, + this.unionMembershipMap, + this.classifier, + this.intersectionTypes + ); + this.schemaMap = generator.generate(); + } + return this.schemaMap; + } + + // ============================================================================ + // SKILL.md Generation + // ============================================================================ + + private generateSkillMd(schemaMap: SchemaMap): SkillGeneratedFile { + const lines: string[] = []; + + // Frontmatter + lines.push(generateFrontmatter({ + name: 'mcp-php-schema', + description: 'Navigate and understand the MCP PHP schema. Use when implementing MCP clients/servers, understanding protocol types, or finding the right DTO for a task.', + })); + lines.push(''); + + // Title + lines.push('# MCP PHP Schema Reference'); + lines.push(''); + + // Quick Navigation + lines.push('## Quick Navigation'); + lines.push(''); + lines.push('- **Server types** (resources, tools, prompts): [reference/server.md](reference/server.md)'); + lines.push('- **Client types** (sampling, elicitation, roots): [reference/client.md](reference/client.md)'); + lines.push('- **Common types** (protocol, JSON-RPC): [reference/common.md](reference/common.md)'); + lines.push('- **RPC methods**: [reference/rpc-methods.md](reference/rpc-methods.md)'); + lines.push('- **Factories**: [reference/factories.md](reference/factories.md)'); + lines.push(''); + + // Schema Structure + const domainStats = this.getDomainStats(schemaMap); + const totalTypes = domainStats.reduce((sum, d) => sum + d.types, 0); + const totalSubdomains = Object.keys(schemaMap.domains).length; + + lines.push('## Schema Structure'); + lines.push(''); + lines.push(`${domainStats.length} domains, ${totalSubdomains} subdomains, ${totalTypes} types total.`); + lines.push(''); + + // Domains Overview + lines.push('### Domains Overview'); + lines.push(''); + lines.push(generateDomainOverviewTable(domainStats)); + lines.push(''); + + // Common Patterns + lines.push('## Common Patterns'); + lines.push(''); + lines.push('### Finding a Request/Result Pair'); + lines.push(''); + lines.push('1. Check [rpc-methods.md](reference/rpc-methods.md) for method name'); + lines.push('2. Look up request type in domain file'); + lines.push('3. Find corresponding result type'); + lines.push(''); + lines.push('### Using Factory Classes'); + lines.push(''); + lines.push('Factories create the correct DTO from discriminator values.'); + lines.push('See [factories.md](reference/factories.md) for patterns.'); + lines.push(''); + + // JSON Data + lines.push('## JSON Data Files'); + lines.push(''); + lines.push('For programmatic access:'); + lines.push(''); + lines.push('- `data/schema-index.json` - Lightweight discovery index'); + lines.push('- `data/schema-common.json` - Common domain types'); + lines.push('- `data/schema-server.json` - Server domain types'); + lines.push('- `data/schema-client.json` - Client domain types'); + lines.push(''); + + // Search Scripts + lines.push('## Search Scripts'); + lines.push(''); + lines.push('```bash'); + lines.push('# Search types by name'); + lines.push('./scripts/search-types.sh "Resource"'); + lines.push(''); + lines.push('# Get type details'); + lines.push('./scripts/get-type.sh "CallToolRequest"'); + lines.push(''); + lines.push('# Find RPC method'); + lines.push('./scripts/find-rpc.sh "tools/call"'); + lines.push('```'); + lines.push(''); + + return { + path: `${this.outputDir}/SKILL.md`, + content: lines.join('\n'), + type: 'markdown', + }; + } + + // ============================================================================ + // Reference Markdown Generation + // ============================================================================ + + private generateOverviewMd(schemaMap: SchemaMap): SkillGeneratedFile { + const lines: string[] = []; + + lines.push('# MCP PHP Schema Overview'); + lines.push(''); + lines.push(`Version: ${schemaMap.version}`); + lines.push(`Namespace: \`${schemaMap.namespace}\``); + lines.push(''); + + // Architecture + lines.push('## Architecture'); + lines.push(''); + lines.push('The schema follows the Model Context Protocol specification.'); + lines.push('Types are organized into three domains:'); + lines.push(''); + lines.push('- **Common**: Base types, JSON-RPC, content blocks'); + lines.push('- **Server**: Resources, tools, prompts, logging'); + lines.push('- **Client**: Sampling, elicitation, roots, tasks'); + lines.push(''); + + // Type Hierarchy + lines.push('## Type Hierarchy'); + lines.push(''); + lines.push('```'); + lines.push('Request (base for all requests)'); + lines.push('├── PaginatedRequest'); + lines.push('├── [Domain]Request types'); + lines.push(''); + lines.push('Result (base for all results)'); + lines.push('├── PaginatedResult'); + lines.push('├── [Domain]Result types'); + lines.push(''); + lines.push('Notification (base for notifications)'); + lines.push('├── [Domain]Notification types'); + lines.push('```'); + lines.push(''); + + // Union Interfaces + lines.push('## Union Interfaces'); + lines.push(''); + lines.push('Union types are represented as interfaces with factory classes:'); + lines.push(''); + lines.push('| Union | Purpose |'); + lines.push('| --- | --- |'); + lines.push('| `ClientRequestInterface` | All requests from client to server |'); + lines.push('| `ServerRequestInterface` | All requests from server to client |'); + lines.push('| `ClientResultInterface` | All results for client requests |'); + lines.push('| `ServerResultInterface` | All results for server requests |'); + lines.push('| `ContentBlockInterface` | Text, image, audio, resource content |'); + lines.push(''); + + return { + path: `${this.outputDir}/reference/overview.md`, + content: lines.join('\n'), + type: 'markdown', + }; + } + + private generateDomainMd(domain: McpDomain, schemaMap: SchemaMap): SkillGeneratedFile { + const lines: string[] = []; + const domainLower = domain.toLowerCase(); + + lines.push(`# ${domain} Domain Types`); + lines.push(''); + + // Get subdomains for this domain + const subdomains = this.getSubdomainsForDomain(domain, schemaMap); + + // Table of contents + const tocSections = subdomains.map((sd) => ({ + name: sd.name, + count: sd.types.length, + })); + lines.push(generateTableOfContents(tocSections)); + + // Generate each subdomain section + for (const subdomain of subdomains) { + lines.push(generateSubdomainSection(subdomain)); + } + + return { + path: `${this.outputDir}/reference/${domainLower}.md`, + content: lines.join('\n'), + type: 'markdown', + }; + } + + private generateRpcMethodsMd(schemaMap: SchemaMap): SkillGeneratedFile { + const lines: string[] = []; + + lines.push('# RPC Methods Reference'); + lines.push(''); + + // Group by direction + const clientToServer: SkillRpcEntry[] = []; + const serverToClient: SkillRpcEntry[] = []; + const bidirectional: SkillRpcEntry[] = []; + + for (const [method, rpc] of Object.entries(schemaMap.rpc)) { + const entry: SkillRpcEntry = { + method, + direction: rpc.direction, + request: rpc.request, + params: rpc.params, + result: rpc.result, + }; + + if (rpc.direction === 'client→server') { + clientToServer.push(entry); + } else if (rpc.direction === 'server→client') { + serverToClient.push(entry); + } else { + bidirectional.push(entry); + } + } + + // Client to Server + if (clientToServer.length > 0) { + lines.push('## Client → Server'); + lines.push(''); + lines.push(generateRpcTable(clientToServer)); + lines.push(''); + } + + // Server to Client + if (serverToClient.length > 0) { + lines.push('## Server → Client'); + lines.push(''); + lines.push(generateRpcTable(serverToClient)); + lines.push(''); + } + + // Bidirectional + if (bidirectional.length > 0) { + lines.push('## Bidirectional'); + lines.push(''); + lines.push(generateRpcTable(bidirectional)); + lines.push(''); + } + + return { + path: `${this.outputDir}/reference/rpc-methods.md`, + content: lines.join('\n'), + type: 'markdown', + }; + } + + private generateFactoriesMd(schemaMap: SchemaMap): SkillGeneratedFile { + const lines: string[] = []; + + lines.push('# Factory Classes Reference'); + lines.push(''); + lines.push('Factories instantiate the correct DTO based on discriminator values.'); + + // Group factories by domain + const factoriesByDomain = new Map(); + + for (const [name, factory] of Object.entries(schemaMap.factories)) { + const domain = factory.domain; + if (!factoriesByDomain.has(domain)) { + factoriesByDomain.set(domain, []); + } + + factoriesByDomain.get(domain)!.push({ + name, + interface: factory.interface, + discriminator: factory.discriminator, + mappings: Object.entries(factory.mappings).map(([value, type]) => ({ + value, + type, + })), + }); + } + + // Generate sections by domain + for (const [domain, factories] of factoriesByDomain) { + lines.push(''); + lines.push(`## ${domain} Factories`); + lines.push(''); + + for (const factory of factories) { + lines.push(generateFactorySection(factory)); + } + } + + return { + path: `${this.outputDir}/reference/factories.md`, + content: lines.join('\n'), + type: 'markdown', + }; + } + + // ============================================================================ + // JSON Data Generation + // ============================================================================ + + private generateSchemaIndex(schemaMap: SchemaMap): SkillGeneratedFile { + // Build lightweight index + const domains: Record = {}; + for (const key of Object.keys(schemaMap.domains)) { + const [domainName, subdomain] = key.split('/'); + if (domainName) { + if (!domains[domainName]) { + domains[domainName] = []; + } + if (subdomain && !domains[domainName].includes(subdomain)) { + domains[domainName].push(subdomain); + } + } + } + + const rpcMethods = Object.entries(schemaMap.rpc).map(([method, rpc]) => ({ + method, + direction: rpc.direction, + })); + + // Build entry points + const entryPoints: Record = {}; + for (const [key, domain] of Object.entries(schemaMap.domains)) { + if (domain.entryPoints.length > 0) { + entryPoints[key] = domain.entryPoints[0] ?? ''; + } + } + + const index: SkillSchemaIndex = { + version: schemaMap.version, + namespace: schemaMap.namespace, + summary: { + types: Object.keys(schemaMap.types).length, + domains: Object.keys(domains).length, + subdomains: Object.keys(schemaMap.domains).length, + rpcMethods: rpcMethods.length, + factories: Object.keys(schemaMap.factories).length, + }, + domains, + rpcMethods, + entryPoints, + }; + + return { + path: `${this.outputDir}/data/schema-index.json`, + content: JSON.stringify(index, null, 2), + type: 'json', + }; + } + + private generateDomainJson(domain: McpDomain, schemaMap: SchemaMap): SkillGeneratedFile { + const domainLower = domain.toLowerCase(); + + // Filter types for this domain + const types: Record = {}; + for (const [name, type] of Object.entries(schemaMap.types)) { + if (type.domain === domain) { + types[name] = { + kind: type.kind, + domain: type.domain, + subdomain: type.subdomain, + namespace: type.namespace, + purpose: type.purpose, + extends: type.extends, + implements: type.implements, + properties: type.properties, + usedBy: type.usedBy, + uses: type.uses, + discriminator: type.discriminator, + }; + } + } + + // Filter factories for this domain + const factories: Record = {}; + for (const [name, factory] of Object.entries(schemaMap.factories)) { + if (factory.domain === domain) { + factories[name] = { + interface: factory.interface, + discriminator: factory.discriminator, + mappings: factory.mappings, + }; + } + } + + const data: SkillDomainData = { + version: schemaMap.version, + domain, + types, + factories, + }; + + return { + path: `${this.outputDir}/data/schema-${domainLower}.json`, + content: JSON.stringify(data, null, 2), + type: 'json', + }; + } + + // ============================================================================ + // Search Scripts Generation + // ============================================================================ + + private generateSearchTypesScript(): SkillGeneratedFile { + const script = `#!/bin/bash +# Search for types by name or property +# Usage: ./search-types.sh [domain] +# Output: Matching type names and locations + +SKILL_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)" +PATTERN="$1" +DOMAIN="\${2:-all}" + +if [ -z "$PATTERN" ]; then + echo "Usage: ./search-types.sh [domain]" + echo " domain: all, common, server, client" + exit 1 +fi + +search_file() { + local file="$1" + if [ -f "$file" ]; then + jq -r ".types | to_entries[] | select(.key | test(\\"$PATTERN\\"; \\"i\\")) | \\"\\(.key) (\\(.value.domain)/\\(.value.subdomain))\\"" "$file" 2>/dev/null + fi +} + +if [ "$DOMAIN" = "all" ]; then + for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + search_file "$f" + fi + done +else + search_file "$SKILL_DIR/data/schema-$DOMAIN.json" +fi +`; + + return { + path: `${this.outputDir}/scripts/search-types.sh`, + content: script, + type: 'script', + }; + } + + private generateGetTypeScript(): SkillGeneratedFile { + const script = `#!/bin/bash +# Get full details for a specific type +# Usage: ./get-type.sh +# Output: JSON with type details, relationships, usage + +SKILL_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)" +TYPE_NAME="$1" + +if [ -z "$TYPE_NAME" ]; then + echo "Usage: ./get-type.sh " + exit 1 +fi + +for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + result=$(jq -r ".types.\\"$TYPE_NAME\\" // empty" "$f" 2>/dev/null) + if [ -n "$result" ]; then + echo "$result" | jq . + exit 0 + fi + fi +done + +echo "Type '$TYPE_NAME' not found" +exit 1 +`; + + return { + path: `${this.outputDir}/scripts/get-type.sh`, + content: script, + type: 'script', + }; + } + + private generateFindRpcScript(): SkillGeneratedFile { + const script = `#!/bin/bash +# Find RPC method details +# Usage: ./find-rpc.sh +# Output: Request/Result types for matching methods + +SKILL_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)" +PATTERN="$1" + +if [ -z "$PATTERN" ]; then + echo "Usage: ./find-rpc.sh " + exit 1 +fi + +jq -r ".rpcMethods[] | select(.method | test(\\"$PATTERN\\"; \\"i\\")) | \\"\\(.method): \\(.direction)\\"" "$SKILL_DIR/data/schema-index.json" 2>/dev/null + +# Also search for full details in domain files +for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + jq -r ".types | to_entries[] | select(.key | endswith(\\"Request\\")) | select(.value.discriminator.value | test(\\"$PATTERN\\"; \\"i\\") // false) | \\"\\(.value.discriminator.value): \\(.key) → \\(.key | sub(\\"Request$\\"; \\"Result\\"))\\"" "$f" 2>/dev/null + fi +done +`; + + return { + path: `${this.outputDir}/scripts/find-rpc.sh`, + content: script, + type: 'script', + }; + } + + // ============================================================================ + // Helper Methods + // ============================================================================ + + private getDomainStats(schemaMap: SchemaMap): Array<{ name: string; types: number; purpose: string }> { + const domainPurposes: Record = { + Common: 'Protocol base, JSON-RPC, content blocks', + Server: 'Tools, resources, prompts, logging', + Client: 'Sampling, elicitation, roots, tasks', + }; + + const stats = new Map(); + + for (const type of Object.values(schemaMap.types)) { + const count = stats.get(type.domain) ?? 0; + stats.set(type.domain, count + 1); + } + + return ['Common', 'Server', 'Client'].map((domain) => ({ + name: domain, + types: stats.get(domain) ?? 0, + purpose: domainPurposes[domain] ?? '', + })); + } + + private getSubdomainsForDomain( + domain: McpDomain, + schemaMap: SchemaMap + ): SkillSubdomainSection[] { + const subdomains = new Map(); + const relationships = new Map>(); + + // Group types by subdomain + for (const [name, type] of Object.entries(schemaMap.types)) { + if (type.domain !== domain) { + continue; + } + + if (!subdomains.has(type.subdomain)) { + subdomains.set(type.subdomain, []); + relationships.set(type.subdomain, new Set()); + } + + subdomains.get(type.subdomain)!.push({ + name, + purpose: extractFirstSentence(type.purpose), + keyProperties: formatKeyProperties(type.properties), + }); + + // Track relationships + const rels = relationships.get(type.subdomain)!; + if (type.extends) { + rels.add(`\`${name}\` extends \`${type.extends}\``); + } + for (const impl of type.implements) { + rels.add(`\`${name}\` implements \`${impl}\``); + } + } + + // Convert to array and sort + const sections: SkillSubdomainSection[] = []; + for (const [name, types] of subdomains) { + sections.push({ + name, + types: types.sort((a, b) => a.name.localeCompare(b.name)), + relationships: Array.from(relationships.get(name) ?? []).slice(0, 5), + }); + } + + return sections.sort((a, b) => a.name.localeCompare(b.name)); + } +} diff --git a/generator/src/generators/skill-markdown.ts b/generator/src/generators/skill-markdown.ts new file mode 100644 index 0000000..8d2fe66 --- /dev/null +++ b/generator/src/generators/skill-markdown.ts @@ -0,0 +1,219 @@ +/** + * MCP PHP Schema Generator - Skill Markdown Helpers + * + * Utility functions for generating markdown content for the Claude Code skill. + */ + +import type { + SkillTypeTableEntry, + SkillSubdomainSection, + SkillRpcEntry, + SkillFactoryEntry, +} from '../types/skill-types.js'; + +// ============================================================================ +// Table Generation +// ============================================================================ + +/** + * Generates a markdown table from headers and rows. + */ +export function generateMarkdownTable( + headers: readonly string[], + rows: readonly (readonly string[])[] +): string { + const lines: string[] = []; + + // Header row + lines.push(`| ${headers.join(' | ')} |`); + + // Separator row + lines.push(`| ${headers.map(() => '---').join(' | ')} |`); + + // Data rows + for (const row of rows) { + // Escape pipe characters in cell values + const escapedRow = row.map((cell) => cell.replace(/\|/g, '\\|')); + lines.push(`| ${escapedRow.join(' | ')} |`); + } + + return lines.join('\n'); +} + +/** + * Generates a types table for a subdomain section. + */ +export function generateTypesTable(types: readonly SkillTypeTableEntry[]): string { + const headers = ['Type', 'Purpose', 'Key Properties']; + const rows = types.map((t) => [t.name, t.purpose, t.keyProperties]); + return generateMarkdownTable(headers, rows); +} + +/** + * Generates an RPC methods table. + */ +export function generateRpcTable(methods: readonly SkillRpcEntry[]): string { + const headers = ['Method', 'Direction', 'Request', 'Result']; + const rows = methods.map((m) => [ + `\`${m.method}\``, + m.direction, + m.request, + m.result, + ]); + return generateMarkdownTable(headers, rows); +} + +/** + * Generates a factory mappings table. + */ +export function generateFactoryMappingsTable( + mappings: readonly { value: string; type: string }[] +): string { + const headers = ['Value', 'Type']; + const rows = mappings.map((m) => [`\`${m.value}\``, m.type]); + return generateMarkdownTable(headers, rows); +} + +// ============================================================================ +// Section Generation +// ============================================================================ + +/** + * Generates a subdomain section with types and relationships. + */ +export function generateSubdomainSection(section: SkillSubdomainSection): string { + const lines: string[] = []; + + lines.push(`## ${section.name}`); + lines.push(''); + lines.push('### Types'); + lines.push(''); + lines.push(generateTypesTable(section.types)); + lines.push(''); + + if (section.relationships.length > 0) { + lines.push('### Relationships'); + lines.push(''); + for (const rel of section.relationships) { + lines.push(`- ${rel}`); + } + lines.push(''); + } + + return lines.join('\n'); +} + +/** + * Generates a factory section. + */ +export function generateFactorySection(factory: SkillFactoryEntry): string { + const lines: string[] = []; + + lines.push(`### ${factory.name}`); + lines.push(''); + lines.push(`- **Interface:** \`${factory.interface}\``); + lines.push(`- **Discriminator:** \`${factory.discriminator}\``); + lines.push(''); + lines.push('**Mappings:**'); + lines.push(''); + lines.push(generateFactoryMappingsTable(factory.mappings)); + lines.push(''); + + return lines.join('\n'); +} + +// ============================================================================ +// Content Generation +// ============================================================================ + +/** + * Generates table of contents from headings. + */ +export function generateTableOfContents( + sections: readonly { name: string; count: number }[] +): string { + const lines: string[] = []; + + lines.push('## Contents'); + lines.push(''); + + for (const section of sections) { + const anchor = section.name.toLowerCase().replace(/\s+/g, '-'); + lines.push(`- [${section.name}](#${anchor}) (${section.count} types)`); + } + + lines.push(''); + + return lines.join('\n'); +} + +/** + * Generates a domain overview table. + */ +export function generateDomainOverviewTable( + domains: readonly { name: string; types: number; purpose: string }[] +): string { + const headers = ['Domain', 'Types', 'Purpose']; + const rows = domains.map((d) => [d.name, String(d.types), d.purpose]); + return generateMarkdownTable(headers, rows); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * Truncates a string to a maximum length with ellipsis. + */ +export function truncate(str: string, maxLength: number): string { + if (str.length <= maxLength) { + return str; + } + return str.substring(0, maxLength - 3) + '...'; +} + +/** + * Converts a property record to a key properties string. + */ +export function formatKeyProperties( + properties: Record, + maxProps = 3 +): string { + const entries = Object.entries(properties); + if (entries.length === 0) { + return '-'; + } + + const selected = entries.slice(0, maxProps); + const formatted = selected.map(([name, type]) => { + const isOptional = type.endsWith('?'); + const cleanType = isOptional ? type.slice(0, -1) : type; + return `${name}${isOptional ? '?' : ''}: ${truncate(cleanType, 15)}`; + }); + + if (entries.length > maxProps) { + formatted.push(`+${entries.length - maxProps} more`); + } + + return formatted.join(', '); +} + +/** + * Extracts the first sentence from a description. + */ +export function extractFirstSentence(text: string, maxLength = 60): string { + const firstSentence = text.split(/[.\n]/)[0]?.trim() ?? text; + return truncate(firstSentence, maxLength); +} + +/** + * Generates markdown frontmatter. + */ +export function generateFrontmatter(metadata: Record): string { + const lines: string[] = ['---']; + for (const [key, value] of Object.entries(metadata)) { + lines.push(`${key}: ${value}`); + } + lines.push('---'); + return lines.join('\n'); +} diff --git a/generator/src/generators/type-alias-wrapper.ts b/generator/src/generators/type-alias-wrapper.ts new file mode 100644 index 0000000..6e06209 --- /dev/null +++ b/generator/src/generators/type-alias-wrapper.ts @@ -0,0 +1,282 @@ +/** + * MCP PHP Schema Generator - Type Alias Wrapper Generator + * + * Generates PHP wrapper classes for TypeScript type aliases that are referenced + * in union types but don't have their own DTOs generated. + * + * ## Problem This Solves + * + * In TypeScript, a type alias like `type EmptyResult = Result` creates an alias + * that's interchangeable with `Result`. However, when `EmptyResult` appears in + * a union type like: + * + * ```typescript + * type ServerResult = EmptyResult | InitializeResult | ... + * ``` + * + * The PHP generator creates a `ServerResultInterface` that lists `EmptyResult` + * as a member. But since `EmptyResult` is just a type alias (not an interface), + * no PHP class is generated for it, causing: + * + * 1. Documentation listing non-existent types + * 2. Factory methods that can't route to `EmptyResult` + * 3. No way to type-hint `ServerResultInterface` for empty responses + * + * ## Solution + * + * This generator creates thin wrapper classes that: + * 1. Extend the aliased base class (e.g., `Result`) + * 2. Implement all union interfaces that reference the alias + * 3. Provide semantic meaning while maintaining compatibility + * + * For example, `EmptyResult` becomes: + * + * ```php + * class EmptyResult extends Result implements ServerResultInterface, ClientResultInterface + * { + * // Inherits everything from Result + * } + * ``` + */ + +import type { TsTypeAlias, TsInterface, GeneratorConfig, DomainClassification, UnionMembershipInfo } from '../types/index.js'; +import { DomainClassifier } from './domain-classifier.js'; +import { formatPhpDocDescription } from './index.js'; + +/** + * Information about a type alias that needs a wrapper class. + */ +export interface TypeAliasWrapperInfo { + /** The type alias name (e.g., 'EmptyResult') */ + readonly aliasName: string; + /** The base type it aliases (e.g., 'Result') */ + readonly baseType: string; + /** Union interfaces this alias should implement */ + readonly unionInterfaces: readonly UnionMembershipInfo[]; + /** Original type alias for description/tags */ + readonly typeAlias: TsTypeAlias; +} + +/** + * Generates PHP wrapper classes for type aliases referenced in unions. + */ +export class TypeAliasWrapperGenerator { + private readonly classifier: DomainClassifier; + private readonly config: GeneratorConfig; + private readonly interfaces: readonly TsInterface[]; + private readonly typeAliases: readonly TsTypeAlias[]; + + constructor( + config: GeneratorConfig, + interfaces: readonly TsInterface[], + typeAliases: readonly TsTypeAlias[] + ) { + this.config = config; + this.classifier = new DomainClassifier(); + this.interfaces = interfaces; + this.typeAliases = typeAliases; + } + + /** + * Finds all type aliases that need wrapper classes. + * + * A type alias needs a wrapper when: + * 1. It's a simple alias (not a union): `type Foo = Bar` + * 2. The base type is an interface that has a generated DTO + * 3. The alias is referenced as a member of at least one union type + * + * @param _unionMembershipMap - Map of type names to their union memberships (unused, kept for API consistency) + * @returns Array of type aliases that need wrapper generation + */ + findAliasesNeedingWrappers( + _unionMembershipMap: Map + ): TypeAliasWrapperInfo[] { + const result: TypeAliasWrapperInfo[] = []; + + for (const alias of this.typeAliases) { + // Skip unions (contain |) - they get their own interface generation + if (alias.type.includes('|')) { + continue; + } + + // Skip string literals (enums) + if (alias.type.startsWith('"') || alias.type.startsWith("'")) { + continue; + } + + const baseType = alias.type.trim(); + + // Check if base type is an interface (has a generated DTO) + const baseInterface = this.interfaces.find((i) => i.name === baseType); + if (!baseInterface) { + continue; + } + + // Check if this alias is referenced in any union + // The union generator extracts members by name, so if 'EmptyResult' appears + // in a union like 'EmptyResult | Foo | Bar', it will be in the membership map + // But wait - the membership map is built from union members, which ARE the alias names + // So we need to check if the alias NAME (not base type) is in any union + const unionMemberships = this.findUnionMembershipsForAlias(alias.name); + + if (unionMemberships.length > 0) { + result.push({ + aliasName: alias.name, + baseType, + unionInterfaces: unionMemberships, + typeAlias: alias, + }); + } + } + + return result; + } + + /** + * Finds all unions that reference a type alias by name. + * + * Scans all union type aliases to find which ones include the given name + * as a member. + */ + private findUnionMembershipsForAlias(aliasName: string): UnionMembershipInfo[] { + const memberships: UnionMembershipInfo[] = []; + + for (const alias of this.typeAliases) { + // Only look at unions + if (!alias.type.includes('|')) { + continue; + } + + // Extract member names from the union + const members = alias.type + .split('|') + .map((m) => m.trim()) + .filter((m) => m.length > 0); + + // Check if our alias is a member + if (members.includes(aliasName)) { + const classification = this.classifier.classify(alias.name, alias.tags); + memberships.push({ + unionName: alias.name, + namespace: `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}\\Union`, + // Type alias wrappers typically don't have discriminator values + // since they represent "empty" or "base" responses + discriminatorField: undefined, + discriminatorValue: undefined, + }); + } + } + + return memberships; + } + + /** + * Generates PHP wrapper class code for a type alias. + */ + generate(info: TypeAliasWrapperInfo): string { + const classification = this.classifier.classify(info.aliasName, info.typeAlias.tags); + const indent = this.getIndent(); + + return this.renderWrapperClass(info, classification, indent); + } + + /** + * Gets the indentation string. + */ + private getIndent(): string { + if (this.config.output.indentation === 'tabs') { + return '\t'; + } + return ' '.repeat(this.config.output.indentSize); + } + + /** + * Renders the PHP wrapper class. + */ + private renderWrapperClass( + info: TypeAliasWrapperInfo, + classification: DomainClassification, + indent: string + ): string { + const lines: string[] = []; + const namespace = `${this.config.output.namespace}\\${classification.domain}\\${classification.subdomain}`; + + // Find the base class's classification to build its namespace + const baseInterface = this.interfaces.find((i) => i.name === info.baseType); + const baseClassification = baseInterface + ? this.classifier.classify(info.baseType, baseInterface.tags) + : classification; + const baseNamespace = `${this.config.output.namespace}\\${baseClassification.domain}\\${baseClassification.subdomain}`; + + // PHP opening tag + lines.push(' `${u.unionName}Interface`).join(', '); + lines.push(`class ${info.aliasName} extends ${info.baseType} implements ${implementsList}`); + lines.push('{'); + + // The wrapper class inherits everything from the base class. + // We only need to add a comment explaining this is intentionally empty. + lines.push(`${indent}// Inherits all functionality from ${info.baseType}.`); + lines.push(`${indent}// This wrapper exists solely to implement union interfaces for type safety.`); + + lines.push('}'); + lines.push(''); + + return lines.join('\n'); + } + + /** + * Gets the output path for a wrapper class. + */ + getOutputPath(info: TypeAliasWrapperInfo): string { + const classification = this.classifier.classify(info.aliasName, info.typeAlias.tags); + return `${classification.domain}/${classification.subdomain}/${info.aliasName}.php`; + } +} diff --git a/generator/src/generators/type-mapper.ts b/generator/src/generators/type-mapper.ts index d4ee701..6f4a63f 100644 --- a/generator/src/generators/type-mapper.ts +++ b/generator/src/generators/type-mapper.ts @@ -9,13 +9,13 @@ import type { PhpType, TsConstant } from '../types/index.js'; /** * Map of constant names to their values, used for resolving typeof expressions. */ -export type ConstantsMap = ReadonlyMap; +export type ConstantsMap = ReadonlyMap; /** * Creates a constants map from an array of TsConstant objects. */ export function createConstantsMap(constants: readonly TsConstant[] | undefined): ConstantsMap { - const map = new Map(); + const map = new Map(); if (constants) { for (const c of constants) { map.set(c.name, c.value); @@ -132,11 +132,17 @@ export class TypeMapper { if (this.isInlineObjectType(trimmed)) { // Check if it's an index signature { [key: string]: T } if (/^\{\s*\[/.test(trimmed)) { + const valueType = this.extractIndexSignatureValueType(trimmed); + const phpDocType = valueType === 'string' ? 'array' : + valueType === 'object' ? 'array' : + 'array'; return { type: 'array', nullable: false, isArray: true, - phpDocType: 'array', + phpDocType, + isIndexSignature: true, + indexSignatureValueType: valueType, }; } // Regular inline object @@ -349,6 +355,26 @@ export class TypeMapper { return depth === 0 && type.endsWith('}'); } + /** + * Extracts the value type from an index signature like { [key: string]: T }. + * + * @param type - The full index signature type string + * @returns The value type: 'string', 'object', or 'mixed' + */ + private static extractIndexSignatureValueType(type: string): 'string' | 'object' | 'mixed' { + // Match { [key: string]: T } or { [key: string]: T; } and extract T + // Pattern: { [identifier: string]: valueType } + // The value type can be: string, unknown, object, or complex types + const match = type.match(/\[\s*\w+\s*:\s*\w+\s*\]\s*:\s*(\w+)/); + if (match?.[1]) { + const valueType = match[1].toLowerCase(); + if (valueType === 'string') return 'string'; + if (valueType === 'object') return 'object'; + } + // Default to mixed for unknown, any, or complex value types + return 'mixed'; + } + /** * Checks if a property name suggests an integer type. */ diff --git a/generator/src/index.ts b/generator/src/index.ts index 8ffbd7f..4d00fa0 100644 --- a/generator/src/index.ts +++ b/generator/src/index.ts @@ -9,7 +9,7 @@ import type { GeneratorConfig, GenerationResult, GeneratedFile, GenerationStats, GenerationError, TsInterface, TsTypeAlias, UnionMembershipMap, UnionMembershipInfo, VersionTracker } from './types/index.js'; import { fetchSchema, fetchSchemaFresh } from './fetcher/index.js'; import { parseSchema } from './parser/index.js'; -import { DtoGenerator, EnumGenerator, UnionGenerator, FactoryGenerator, BuilderGenerator, ContractGenerator, DomainClassifier, createConstantsMap } from './generators/index.js'; +import { DtoGenerator, EnumGenerator, NumericEnumGenerator, ConstantsGenerator, UnionGenerator, FactoryGenerator, BuilderGenerator, ContractGenerator, TypeAliasWrapperGenerator, IntersectionTypeWrapperGenerator, DomainClassifier, createConstantsMap, SkillGenerator } from './generators/index.js'; import { FileWriter, generateAbstractDto, generateAbstractEnum, generateValidatesRequiredFieldsTrait } from './writers/index.js'; import { SyntheticDtoExtractor, updateInterfacesWithSyntheticTypes } from './extractors/index.js'; import { buildVersionTracker, createEmptyVersionTracker } from './version-tracker/index.js'; @@ -47,7 +47,8 @@ export { fetchSchema, fetchSchemaFresh, clearCache } from './fetcher/index.js'; export { parseSchema, parseSchemaFile, resolveInheritance, getCategoryTag } from './parser/index.js'; // Re-export generators -export { DtoGenerator, EnumGenerator, UnionGenerator, FactoryGenerator, BuilderGenerator, ContractGenerator, TypeMapper, DomainClassifier } from './generators/index.js'; +export { DtoGenerator, EnumGenerator, NumericEnumGenerator, ConstantsGenerator, UnionGenerator, FactoryGenerator, BuilderGenerator, ContractGenerator, TypeAliasWrapperGenerator, IntersectionTypeWrapperGenerator, TypeMapper, DomainClassifier, SchemaMapGenerator } from './generators/index.js'; +export type { TypeAliasWrapperInfo, IntersectionTypeWrapperInfo, SchemaMap, SchemaMapType, SchemaMapFactory, SchemaMapRpc, SchemaMapDomain, SchemaMapIndex } from './generators/index.js'; // Re-export writers export { FileWriter, generateAbstractDto, generateAbstractEnum, generateValidatesRequiredFieldsTrait } from './writers/index.js'; @@ -334,6 +335,22 @@ export async function generate( type: 'dto', // Categorize as 'dto' since it's used by DTOs }); + // Step 5.5: Generate McpConstants class from schema constants + // This includes protocol versions, JSON-RPC version, and error codes + if (ast.constants && ast.constants.length > 0) { + progress('Generating constants class...'); + const constantsGenerator = new ConstantsGenerator(config); + files.push({ + path: constantsGenerator.getOutputPath(), + content: constantsGenerator.generate(ast.constants), + type: 'enum', // Categorize as 'enum' since it's a constants class + }); + + if (config.verbose) { + progress(`Generated McpConstants with ${ast.constants.length} constants`); + } + } + // Step 6: Generate DTOs from interfaces (including synthetic ones) // First pass: classify all non-synthetic interfaces to populate cache progress('Generating DTOs...'); @@ -395,6 +412,92 @@ export async function generate( } } + // Step 7.1: Generate numeric enums from TypeScript enum declarations + // These are actual TypeScript enums (like ErrorCode) with numeric values, + // different from string literal union types handled by EnumGenerator. + progress('Generating numeric enums...'); + const numericEnumGenerator = new NumericEnumGenerator(config); + const numericEnums = ast.enums?.filter((e) => numericEnumGenerator.isNumericEnum(e)) ?? []; + + if (config.verbose) { + progress(`Found ${numericEnums.length} numeric enums to generate`); + } + + for (const tsEnum of numericEnums) { + try { + const content = numericEnumGenerator.generate(tsEnum); + const classification = numericEnumGenerator.classify(tsEnum); + const path = writer.getOutputPath(classification, 'Enum', tsEnum.name); + files.push({ path, content, type: 'enum' }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + errors.push({ + type: tsEnum.name, + message: `Failed to generate numeric enum: ${message}`, + source: 'enum', + }); + } + } + + // Step 7.5: Generate type alias wrappers + // Type aliases like `type EmptyResult = Result` don't get their own DTOs, but when + // they're referenced in unions (e.g., `ServerResult = EmptyResult | InitializeResult | ...`), + // we need wrapper classes so they can implement the union interfaces. + // See: https://github.com/WordPress/php-mcp-schema/issues/XX (Missing EmptyResult type) + progress('Generating type alias wrappers...'); + const typeAliasWrapperGenerator = new TypeAliasWrapperGenerator(config, allInterfaces, ast.typeAliases); + const aliasesNeedingWrappers = typeAliasWrapperGenerator.findAliasesNeedingWrappers(unionMembershipMap); + + if (config.verbose) { + progress(`Found ${aliasesNeedingWrappers.length} type aliases needing wrapper classes`); + } + + for (const aliasInfo of aliasesNeedingWrappers) { + try { + const content = typeAliasWrapperGenerator.generate(aliasInfo); + const path = typeAliasWrapperGenerator.getOutputPath(aliasInfo); + files.push({ path, content, type: 'dto' }); // Categorize as DTO since it extends a DTO + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + errors.push({ + type: aliasInfo.aliasName, + message: `Failed to generate type alias wrapper: ${message}`, + source: 'typeAliasWrapper', + }); + } + } + + // Step 7.6: Generate intersection type wrappers + // Intersection types like `type GetTaskResult = Result & Task` combine properties + // from multiple interfaces. When they're referenced in unions + // (e.g., `ClientResult = EmptyResult | GetTaskResult | ...`), we need concrete + // classes that have all properties from the intersected types. + // PHP's single inheritance limitation is handled by: + // - Extending the most "generic" type (typically Result) + // - Merging properties from other intersected types into the class + progress('Generating intersection type wrappers...'); + const intersectionTypeWrapperGenerator = new IntersectionTypeWrapperGenerator(config, allInterfaces, ast.typeAliases); + const intersectionsNeedingWrappers = intersectionTypeWrapperGenerator.findIntersectionsNeedingWrappers(unionMembershipMap); + + if (config.verbose) { + progress(`Found ${intersectionsNeedingWrappers.length} intersection types needing wrapper classes`); + } + + for (const intersectionInfo of intersectionsNeedingWrappers) { + try { + const content = intersectionTypeWrapperGenerator.generate(intersectionInfo); + const path = intersectionTypeWrapperGenerator.getOutputPath(intersectionInfo); + files.push({ path, content, type: 'dto' }); // Categorize as DTO since it extends a DTO + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + errors.push({ + type: intersectionInfo.typeName, + message: `Failed to generate intersection type wrapper: ${message}`, + source: 'intersectionTypeWrapper', + }); + } + } + // Step 8: Generate builders (currently disabled) // TODO: Builder generation can be enabled in the future by setting generateBuilders to true. // The BuilderGenerator class is fully implemented and ready to use. @@ -428,6 +531,42 @@ export async function generate( files.push({ path, content: contract.content, type: 'interface' }); } + // Step 9.5: Generate skill files for Claude Code + // Skill files go to the project root skill/ directory, not inside src/ + progress('Generating skill files...'); + const skillGenerator = new SkillGenerator( + config, + allInterfaces, + ast.typeAliases, + ast.enums ?? [], + unionMembershipMap, + classifier, + intersectionsNeedingWrappers + ); + const skillResult = skillGenerator.generateAll(); + + // Write skill files directly to project root (not via FileWriter which uses output.outputDir) + const { mkdir, writeFile, chmod } = await import('fs/promises'); + const { dirname, join, resolve } = await import('path'); + + // Resolve skill directory relative to output dir's parent (project root) + const projectRoot = resolve(config.output.outputDir, '..'); + + for (const skillFile of skillResult.files) { + const fullPath = join(projectRoot, skillFile.path); + await mkdir(dirname(fullPath), { recursive: true }); + await writeFile(fullPath, skillFile.content, 'utf-8'); + + // Make scripts executable + if (skillFile.type === 'script') { + await chmod(fullPath, 0o755); + } + } + + if (config.verbose) { + progress(`Generated ${skillResult.files.length} skill files (${skillResult.stats.markdownFiles} markdown, ${skillResult.stats.jsonFiles} JSON, ${skillResult.stats.scriptFiles} scripts)`); + } + // Step 10: Validate class names (PSR-1 compliance) progress('Validating class names...'); const invalidClassNames: string[] = []; diff --git a/generator/src/parser/index.ts b/generator/src/parser/index.ts index 4529678..595d265 100644 --- a/generator/src/parser/index.ts +++ b/generator/src/parser/index.ts @@ -217,8 +217,11 @@ export function getCategoryTag(tags: readonly JsDocTag[]): string | undefined { } /** - * Extracts exported string constants from a source file. - * Looks for patterns like: export const CONSTANT_NAME = 'value'; + * Extracts exported constants from a source file. + * Looks for patterns like: + * - export const CONSTANT_NAME = 'value'; (string) + * - export const CONSTANT_NAME = 123; (number) + * - export const CONSTANT_NAME = -32700; (negative number) */ function extractConstants(sourceFile: SourceFile): TsConstant[] { const constants: TsConstant[] = []; @@ -238,8 +241,10 @@ function extractConstants(sourceFile: SourceFile): TsConstant[] { continue; } - // Only extract string literals const initText = initializer.getText(); + const description = getConstantDescription(statement); + + // Check for string literals if ((initText.startsWith("'") && initText.endsWith("'")) || (initText.startsWith('"') && initText.endsWith('"'))) { // Remove quotes to get the actual value @@ -247,7 +252,20 @@ function extractConstants(sourceFile: SourceFile): TsConstant[] { constants.push({ name: declaration.getName(), value, - description: getConstantDescription(statement), + valueType: 'string', + description, + }); + continue; + } + + // Check for numeric literals (including negative numbers) + const numericMatch = initText.match(/^-?\d+$/); + if (numericMatch) { + constants.push({ + name: declaration.getName(), + value: parseInt(initText, 10), + valueType: 'number', + description, }); } } diff --git a/generator/src/types/index.ts b/generator/src/types/index.ts index d32f821..4e79215 100644 --- a/generator/src/types/index.ts +++ b/generator/src/types/index.ts @@ -61,7 +61,8 @@ export interface TsTypeAlias { */ export interface TsConstant { readonly name: string; - readonly value: string; + readonly value: string | number; + readonly valueType: 'string' | 'number'; readonly description?: string; } @@ -154,6 +155,18 @@ export interface PhpType { * Used for PHP 7.4 compatibility (e.g., `mixed` is PHP 8.0+). */ readonly isUntyped?: boolean; + /** + * When true, this type represents an index signature like { [key: string]: T }. + * Used to select the appropriate validation helper in fromArray(). + */ + readonly isIndexSignature?: boolean; + /** + * The value type of an index signature. + * - 'string': { [key: string]: string } -> use asStringMap() helper + * - 'object': { [key: string]: object } -> use asArray() helper + * - 'mixed': { [key: string]: unknown } -> use asArray() helper + */ + readonly indexSignatureValueType?: 'string' | 'object' | 'mixed'; } /** @@ -166,6 +179,12 @@ export interface PhpProperty { readonly isRequired: boolean; readonly defaultValue?: string; readonly constValue?: string; + /** + * Maximum number of items allowed in an array property. + * Extracted from JSDoc comments like "Must not exceed N items". + * Used to generate validation in fromArray(). + */ + readonly maxItems?: number; } /** @@ -360,3 +379,24 @@ export interface VersionTracker { /** Check if a definition was modified after introduction */ wasModified(definitionName: string): boolean; } + +// ============================================================================ +// Skill Types (Re-exported) +// ============================================================================ + +export type { + SkillConfig, + SkillSchemaIndex, + SkillSchemaSummary, + SkillRpcMethod, + SkillTypeInfo, + SkillFactoryInfo, + SkillDomainData, + SkillTypeTableEntry, + SkillSubdomainSection, + SkillRpcEntry, + SkillFactoryEntry, + SkillGeneratedFile, + SkillGenerationResult, + SkillGenerationStats, +} from './skill-types.js'; diff --git a/generator/src/types/skill-types.ts b/generator/src/types/skill-types.ts new file mode 100644 index 0000000..85511f3 --- /dev/null +++ b/generator/src/types/skill-types.ts @@ -0,0 +1,174 @@ +/** + * MCP PHP Schema Generator - Skill Types + * + * Type definitions for the Claude Code skill generator. + * These types represent the output structure for progressive discovery. + */ + +// ============================================================================ +// Skill Configuration Types +// ============================================================================ + +/** + * Configuration for skill generation. + */ +export interface SkillConfig { + /** Output directory for skill files (relative to project root) */ + readonly outputDir: string; + /** Whether to generate skill files */ + readonly enabled: boolean; +} + +// ============================================================================ +// Skill Index Types (Lightweight Discovery) +// ============================================================================ + +/** + * Lightweight schema index for quick discovery. + * Target size: ~2KB + */ +export interface SkillSchemaIndex { + readonly version: string; + readonly namespace: string; + readonly summary: SkillSchemaSummary; + readonly domains: Record; + readonly rpcMethods: SkillRpcMethod[]; + readonly entryPoints: Record; +} + +/** + * High-level summary of the schema. + */ +export interface SkillSchemaSummary { + readonly types: number; + readonly domains: number; + readonly subdomains: number; + readonly rpcMethods: number; + readonly factories: number; +} + +/** + * RPC method entry for the index. + */ +export interface SkillRpcMethod { + readonly method: string; + readonly direction: 'client→server' | 'server→client' | 'bidirectional'; +} + +// ============================================================================ +// Domain Data Types (Split JSON Files) +// ============================================================================ + +/** + * Type information for domain JSON files. + */ +export interface SkillTypeInfo { + readonly kind: 'class' | 'enum' | 'union' | 'factory' | 'constant'; + readonly domain: string; + readonly subdomain: string; + readonly namespace: string; + readonly purpose: string; + readonly extends?: string; + readonly implements: string[]; + readonly properties: Record; + readonly usedBy: string[]; + readonly uses: string[]; + readonly discriminator?: { + readonly field: string; + readonly value?: string; + }; +} + +/** + * Factory information for domain JSON files. + */ +export interface SkillFactoryInfo { + readonly interface: string; + readonly discriminator: string; + readonly mappings: Record; +} + +/** + * Complete domain data structure. + */ +export interface SkillDomainData { + readonly version: string; + readonly domain: string; + readonly types: Record; + readonly factories: Record; +} + +// ============================================================================ +// Markdown Reference Types +// ============================================================================ + +/** + * Type entry for markdown reference tables. + */ +export interface SkillTypeTableEntry { + readonly name: string; + readonly purpose: string; + readonly keyProperties: string; +} + +/** + * Subdomain section for domain reference files. + */ +export interface SkillSubdomainSection { + readonly name: string; + readonly types: SkillTypeTableEntry[]; + readonly relationships: string[]; +} + +/** + * RPC method entry for rpc-methods.md. + */ +export interface SkillRpcEntry { + readonly method: string; + readonly direction: string; + readonly request: string; + readonly params?: string; + readonly result: string; +} + +/** + * Factory entry for factories.md. + */ +export interface SkillFactoryEntry { + readonly name: string; + readonly interface: string; + readonly discriminator: string; + readonly mappings: Array<{ value: string; type: string }>; +} + +// ============================================================================ +// Generated File Types +// ============================================================================ + +/** + * Generated skill file. + */ +export interface SkillGeneratedFile { + readonly path: string; + readonly content: string; + readonly type: 'markdown' | 'json' | 'script'; +} + +/** + * Result from skill generation. + */ +export interface SkillGenerationResult { + readonly files: readonly SkillGeneratedFile[]; + readonly stats: SkillGenerationStats; +} + +/** + * Statistics about skill generation. + */ +export interface SkillGenerationStats { + readonly markdownFiles: number; + readonly jsonFiles: number; + readonly scriptFiles: number; + readonly totalSize: number; + readonly indexSize: number; +} diff --git a/generator/src/writers/index.ts b/generator/src/writers/index.ts index 0f8cc9f..2883954 100644 --- a/generator/src/writers/index.ts +++ b/generator/src/writers/index.ts @@ -582,6 +582,92 @@ ${indent}{ ${indent}${indent}return $value === null ? null : self::asStringArray($value); ${indent}} +${indent}/** +${indent} * Asserts a value is an associative array with string values only. +${indent} * +${indent} * Used for MCP types like { [key: string]: string } index signatures. +${indent} * +${indent} * @param mixed $value +${indent} * @return array +${indent} * @phpstan-assert array $value +${indent} */ +${indent}protected static function asStringMap($value): array +${indent}{ +${indent}${indent}if (!is_array($value)) { +${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf( +${indent}${indent}${indent}${indent}'Expected array, got %s', +${indent}${indent}${indent}${indent}gettype($value) +${indent}${indent}${indent})); +${indent}${indent}} +${indent}${indent}foreach ($value as $key => $v) { +${indent}${indent}${indent}if (!is_string($v)) { +${indent}${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf( +${indent}${indent}${indent}${indent}${indent}'Expected string value for key "%s", got %s', +${indent}${indent}${indent}${indent}${indent}(string) $key, +${indent}${indent}${indent}${indent}${indent}gettype($v) +${indent}${indent}${indent}${indent})); +${indent}${indent}${indent}} +${indent}${indent}} +${indent}${indent}/** @var array */ +${indent}${indent}return $value; +${indent}} + +${indent}/** +${indent} * Returns a value as string map or null. +${indent} * +${indent} * Used for optional MCP types like { [key: string]: string } | null. +${indent} * +${indent} * @param mixed $value +${indent} * @return array|null +${indent} */ +${indent}protected static function asStringMapOrNull($value): ?array +${indent}{ +${indent}${indent}return $value === null ? null : self::asStringMap($value); +${indent}} + +${indent}/** +${indent} * Asserts a value is an associative array with object values only. +${indent} * +${indent} * Used for MCP types like { [key: string]: object } index signatures. +${indent} * +${indent} * @param mixed $value +${indent} * @return array +${indent} * @phpstan-assert array $value +${indent} */ +${indent}protected static function asObjectMap($value): array +${indent}{ +${indent}${indent}if (!is_array($value)) { +${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf( +${indent}${indent}${indent}${indent}'Expected array, got %s', +${indent}${indent}${indent}${indent}gettype($value) +${indent}${indent}${indent})); +${indent}${indent}} +${indent}${indent}foreach ($value as $key => $v) { +${indent}${indent}${indent}if (!is_object($v)) { +${indent}${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf( +${indent}${indent}${indent}${indent}${indent}'Expected object value for key "%s", got %s', +${indent}${indent}${indent}${indent}${indent}(string) $key, +${indent}${indent}${indent}${indent}${indent}gettype($v) +${indent}${indent}${indent}${indent})); +${indent}${indent}${indent}} +${indent}${indent}} +${indent}${indent}/** @var array */ +${indent}${indent}return $value; +${indent}} + +${indent}/** +${indent} * Returns a value as object map or null. +${indent} * +${indent} * Used for optional MCP types like { [key: string]: object } | null. +${indent} * +${indent} * @param mixed $value +${indent} * @return array|null +${indent} */ +${indent}protected static function asObjectMapOrNull($value): ?array +${indent}{ +${indent}${indent}return $value === null ? null : self::asObjectMap($value); +${indent}} + ${indent}/** ${indent} * Asserts a value is a scalar (string, int, float, or bool) for sprintf. ${indent} * @@ -598,6 +684,38 @@ ${indent}${indent}${indent})); ${indent}${indent}} ${indent}${indent}return $value; ${indent}} + +${indent}/** +${indent} * Asserts a value is a string or number (int/float) and returns it. +${indent} * +${indent} * Used for MCP types like ProgressToken that accept string | number. +${indent} * +${indent} * @param mixed $value +${indent} * @return string|int|float +${indent} */ +${indent}protected static function asStringOrNumber($value) +${indent}{ +${indent}${indent}if (!is_string($value) && !is_int($value) && !is_float($value)) { +${indent}${indent}${indent}throw new \\InvalidArgumentException(sprintf( +${indent}${indent}${indent}${indent}'Expected string or number, got %s', +${indent}${indent}${indent}${indent}gettype($value) +${indent}${indent}${indent})); +${indent}${indent}} +${indent}${indent}return $value; +${indent}} + +${indent}/** +${indent} * Returns a value as string or number (int/float), or null. +${indent} * +${indent} * Used for optional MCP types like ProgressToken that accept string | number | null. +${indent} * +${indent} * @param mixed $value +${indent} * @return string|int|float|null +${indent} */ +${indent}protected static function asStringOrNumberOrNull($value) +${indent}{ +${indent}${indent}return $value === null ? null : self::asStringOrNumber($value); +${indent}} } `; } diff --git a/skill/SKILL.md b/skill/SKILL.md new file mode 100644 index 0000000..fcc575e --- /dev/null +++ b/skill/SKILL.md @@ -0,0 +1,61 @@ +--- +name: mcp-php-schema +description: Navigate and understand the MCP PHP schema. Use when implementing MCP clients/servers, understanding protocol types, or finding the right DTO for a task. +--- + +# MCP PHP Schema Reference + +## Quick Navigation + +- **Server types** (resources, tools, prompts): [reference/server.md](reference/server.md) +- **Client types** (sampling, elicitation, roots): [reference/client.md](reference/client.md) +- **Common types** (protocol, JSON-RPC): [reference/common.md](reference/common.md) +- **RPC methods**: [reference/rpc-methods.md](reference/rpc-methods.md) +- **Factories**: [reference/factories.md](reference/factories.md) + +## Schema Structure + +3 domains, 17 subdomains, 164 types total. + +### Domains Overview + +| Domain | Types | Purpose | +| --- | --- | --- | +| Common | 59 | Protocol base, JSON-RPC, content blocks | +| Server | 59 | Tools, resources, prompts, logging | +| Client | 46 | Sampling, elicitation, roots, tasks | + +## Common Patterns + +### Finding a Request/Result Pair + +1. Check [rpc-methods.md](reference/rpc-methods.md) for method name +2. Look up request type in domain file +3. Find corresponding result type + +### Using Factory Classes + +Factories create the correct DTO from discriminator values. +See [factories.md](reference/factories.md) for patterns. + +## JSON Data Files + +For programmatic access: + +- `data/schema-index.json` - Lightweight discovery index +- `data/schema-common.json` - Common domain types +- `data/schema-server.json` - Server domain types +- `data/schema-client.json` - Client domain types + +## Search Scripts + +```bash +# Search types by name +./scripts/search-types.sh "Resource" + +# Get type details +./scripts/get-type.sh "CallToolRequest" + +# Find RPC method +./scripts/find-rpc.sh "tools/call" +``` diff --git a/skill/data/schema-client.json b/skill/data/schema-client.json new file mode 100644 index 0000000..594964e --- /dev/null +++ b/skill/data/schema-client.json @@ -0,0 +1,998 @@ +{ + "version": "2025-11-25", + "domain": "Client", + "types": { + "ClientCapabilities": { + "kind": "class", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\ClientCapabilities", + "purpose": "Capabilities a client may support", + "implements": [], + "properties": { + "experimental": "{ [key: string]: object }?", + "roots": "ClientCapabilitiesRoots | undefined?", + "sampling": "ClientCapabilitiesSampling | undefined?", + "elicitation": "ClientCapabilitiesElicitation | undefined?", + "tasks": "ClientCapabilitiesTasks | undefined?" + }, + "usedBy": [ + "InitializeRequestParams" + ], + "uses": [ + "ClientCapabilitiesRoots", + "ClientCapabilitiesSampling", + "ClientCapabilitiesElicitation", + "ClientCapabilitiesTasks" + ] + }, + "TaskMetadata": { + "kind": "class", + "domain": "Client", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Client\\Tasks\\TaskMetadata", + "purpose": "Metadata for augmenting a request with task execution", + "implements": [], + "properties": { + "ttl": "number?" + }, + "usedBy": [ + "TaskAugmentedRequestParams" + ], + "uses": [] + }, + "RelatedTaskMetadata": { + "kind": "class", + "domain": "Client", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Client\\Tasks\\RelatedTaskMetadata", + "purpose": "Metadata for associating messages with a task", + "implements": [], + "properties": { + "taskId": "string" + }, + "usedBy": [], + "uses": [] + }, + "Task": { + "kind": "class", + "domain": "Client", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Client\\Tasks\\Task", + "purpose": "Data associated with a task", + "implements": [], + "properties": { + "taskId": "string", + "status": "TaskStatus", + "statusMessage": "string?", + "createdAt": "string", + "lastUpdatedAt": "string", + "ttl": "number | null", + "pollInterval": "number?" + }, + "usedBy": [ + "CreateTaskResult", + "ListTasksResult", + "GetTaskResult", + "CancelTaskResult" + ], + "uses": [ + "TaskStatus" + ] + }, + "CreateTaskResult": { + "kind": "class", + "domain": "Client", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Client\\Tasks\\CreateTaskResult", + "purpose": "A response to a task-augmented request", + "extends": "Result", + "implements": [], + "properties": { + "task": "Task" + }, + "usedBy": [], + "uses": [ + "Result", + "Task" + ] + }, + "CreateMessageRequestParams": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\CreateMessageRequestParams", + "purpose": "Parameters for a `sampling/createMessage` request", + "extends": "TaskAugmentedRequestParams", + "implements": [], + "properties": { + "messages": "SamplingMessage[]", + "modelPreferences": "ModelPreferences?", + "systemPrompt": "string?", + "includeContext": "\"none\" | \"thisServer\" | \"allServers\"?", + "temperature": "number?", + "maxTokens": "number", + "stopSequences": "string[]?", + "metadata": "object?", + "tools": "Tool[]?", + "toolChoice": "ToolChoice?" + }, + "usedBy": [ + "CreateMessageRequest" + ], + "uses": [ + "TaskAugmentedRequestParams", + "SamplingMessage", + "ModelPreferences", + "Tool", + "ToolChoice" + ] + }, + "ToolChoice": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\ToolChoice", + "purpose": "Controls tool selection behavior for sampling requests", + "implements": [], + "properties": { + "mode": "\"auto\" | \"required\" | \"none\"?" + }, + "usedBy": [ + "CreateMessageRequestParams" + ], + "uses": [] + }, + "CreateMessageRequest": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\CreateMessageRequest", + "purpose": "A request from the server to sample an LLM via the client", + "extends": "JSONRPCRequest", + "implements": [ + "ServerRequestInterface" + ], + "properties": { + "method": "\"sampling/createMessage\"", + "params": "CreateMessageRequestParams" + }, + "usedBy": [ + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "CreateMessageRequestParams" + ], + "discriminator": { + "field": "method", + "value": "sampling/createMessage" + } + }, + "CreateMessageResult": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\CreateMessageResult", + "purpose": "The client's response to a sampling/createMessage request from the server", + "extends": "Result", + "implements": [ + "ClientResultInterface" + ], + "properties": { + "model": "string", + "stopReason": "\"endTurn\" | \"stopSequence\" | \"maxTokens\" | \"too...?" + }, + "usedBy": [ + "ClientResultInterface" + ], + "uses": [ + "Result", + "SamplingMessage" + ] + }, + "SamplingMessage": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\SamplingMessage", + "purpose": "Describes a message issued to or received from an LLM API", + "implements": [], + "properties": { + "role": "Role", + "content": "SamplingMessageContentBlock | SamplingMessageCo...", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "CreateMessageRequestParams", + "CreateMessageResult" + ], + "uses": [ + "Role", + "SamplingMessageContentBlock" + ] + }, + "ToolUseContent": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\ToolUseContent", + "purpose": "A request from the assistant to call a tool", + "implements": [ + "SamplingMessageContentBlockInterface" + ], + "properties": { + "type": "\"tool_use\"", + "id": "string", + "name": "string", + "input": "{ [key: string]: unknown }", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "SamplingMessageContentBlockInterface" + ], + "uses": [], + "discriminator": { + "field": "type", + "value": "tool_use" + } + }, + "ToolResultContent": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\ToolResultContent", + "purpose": "The result of a tool use, provided by the user back to the assistant", + "implements": [ + "SamplingMessageContentBlockInterface" + ], + "properties": { + "type": "\"tool_result\"", + "toolUseId": "string", + "content": "ContentBlock[]", + "structuredContent": "{ [key: string]: unknown }?", + "isError": "boolean?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "SamplingMessageContentBlockInterface" + ], + "uses": [ + "ContentBlock" + ], + "discriminator": { + "field": "type", + "value": "tool_result" + } + }, + "ModelPreferences": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\ModelPreferences", + "purpose": "The server's preferences for model selection, requested of the client during sampling", + "implements": [], + "properties": { + "hints": "ModelHint[]?", + "costPriority": "number?", + "speedPriority": "number?", + "intelligencePriority": "number?" + }, + "usedBy": [ + "CreateMessageRequestParams" + ], + "uses": [ + "ModelHint" + ] + }, + "ModelHint": { + "kind": "class", + "domain": "Client", + "subdomain": "Sampling", + "namespace": "WP\\McpSchema\\Client\\Sampling\\ModelHint", + "purpose": "Hints to use for model selection", + "implements": [], + "properties": { + "name": "string?" + }, + "usedBy": [ + "ModelPreferences" + ], + "uses": [] + }, + "ListRootsRequest": { + "kind": "class", + "domain": "Client", + "subdomain": "Roots", + "namespace": "WP\\McpSchema\\Client\\Roots\\ListRootsRequest", + "purpose": "Sent from the server to request a list of root URIs from the client", + "extends": "JSONRPCRequest", + "implements": [ + "ServerRequestInterface" + ], + "properties": { + "method": "\"roots/list\"", + "params": "RequestParams?" + }, + "usedBy": [ + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "RequestParams" + ], + "discriminator": { + "field": "method", + "value": "roots/list" + } + }, + "ListRootsResult": { + "kind": "class", + "domain": "Client", + "subdomain": "Roots", + "namespace": "WP\\McpSchema\\Client\\Roots\\ListRootsResult", + "purpose": "The client's response to a roots/list request from the server", + "extends": "Result", + "implements": [ + "ClientResultInterface" + ], + "properties": { + "roots": "Root[]" + }, + "usedBy": [ + "ClientResultInterface" + ], + "uses": [ + "Result", + "Root" + ] + }, + "Root": { + "kind": "class", + "domain": "Client", + "subdomain": "Roots", + "namespace": "WP\\McpSchema\\Client\\Roots\\Root", + "purpose": "Represents a root directory or file that the server can operate on", + "implements": [], + "properties": { + "uri": "string", + "name": "string?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ListRootsResult" + ], + "uses": [] + }, + "RootsListChangedNotification": { + "kind": "class", + "domain": "Client", + "subdomain": "Roots", + "namespace": "WP\\McpSchema\\Client\\Roots\\RootsListChangedNotification", + "purpose": "A notification from the client to the server, informing it that the list of roots has changed", + "extends": "JSONRPCNotification", + "implements": [ + "ClientNotificationInterface" + ], + "properties": { + "method": "\"notifications/roots/list_changed\"", + "params": "NotificationParams?" + }, + "usedBy": [ + "ClientNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "NotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/roots/list_changed" + } + }, + "ElicitRequestFormParams": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitRequestFormParams", + "purpose": "The parameters for a request to elicit non-sensitive information from the user via a form in the client", + "extends": "TaskAugmentedRequestParams", + "implements": [ + "ElicitRequestParamsInterface" + ], + "properties": { + "mode": "\"form\"?", + "message": "string", + "requestedSchema": "ElicitRequestFormParamsRequestedSchema" + }, + "usedBy": [ + "ElicitRequestParamsInterface" + ], + "uses": [ + "TaskAugmentedRequestParams", + "ElicitRequestFormParamsRequestedSchema" + ], + "discriminator": { + "field": "mode", + "value": "form" + } + }, + "ElicitRequestURLParams": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitRequestURLParams", + "purpose": "The parameters for a request to elicit information from the user via a URL in the client", + "extends": "TaskAugmentedRequestParams", + "implements": [ + "ElicitRequestParamsInterface" + ], + "properties": { + "mode": "\"url\"", + "message": "string", + "elicitationId": "string", + "url": "string" + }, + "usedBy": [ + "ElicitRequestParamsInterface" + ], + "uses": [ + "TaskAugmentedRequestParams" + ], + "discriminator": { + "field": "mode", + "value": "url" + } + }, + "ElicitRequest": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitRequest", + "purpose": "A request from the server to elicit additional information from the user via the client", + "extends": "JSONRPCRequest", + "implements": [ + "ServerRequestInterface" + ], + "properties": { + "method": "\"elicitation/create\"", + "params": "ElicitRequestParams" + }, + "usedBy": [ + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "ElicitRequestParams" + ], + "discriminator": { + "field": "method", + "value": "elicitation/create" + } + }, + "StringSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\StringSchema", + "purpose": "String Schema data structure", + "implements": [ + "PrimitiveSchemaDefinitionInterface" + ], + "properties": { + "type": "\"string\"", + "title": "string?", + "description": "string?", + "minLength": "number?", + "maxLength": "number?", + "format": "\"email\" | \"uri\" | \"date\" | \"date-time\"?", + "default": "string?" + }, + "usedBy": [ + "PrimitiveSchemaDefinitionInterface" + ], + "uses": [], + "discriminator": { + "field": "type", + "value": "string" + } + }, + "NumberSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\NumberSchema", + "purpose": "Number Schema data structure", + "implements": [ + "PrimitiveSchemaDefinitionInterface" + ], + "properties": { + "type": "\"number\" | \"integer\"", + "title": "string?", + "description": "string?", + "minimum": "number?", + "maximum": "number?", + "default": "number?" + }, + "usedBy": [ + "PrimitiveSchemaDefinitionInterface" + ], + "uses": [] + }, + "BooleanSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\BooleanSchema", + "purpose": "Boolean Schema data structure", + "implements": [ + "PrimitiveSchemaDefinitionInterface" + ], + "properties": { + "type": "\"boolean\"", + "title": "string?", + "description": "string?", + "default": "boolean?" + }, + "usedBy": [ + "PrimitiveSchemaDefinitionInterface" + ], + "uses": [], + "discriminator": { + "field": "type", + "value": "boolean" + } + }, + "UntitledSingleSelectEnumSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\UntitledSingleSelectEnumSchema", + "purpose": "Schema for single-selection enumeration without display titles for options", + "implements": [ + "SingleSelectEnumSchemaInterface" + ], + "properties": { + "type": "\"string\"", + "title": "string?", + "description": "string?", + "enum": "string[]", + "default": "string?" + }, + "usedBy": [ + "SingleSelectEnumSchemaInterface" + ], + "uses": [], + "discriminator": { + "field": "type", + "value": "string" + } + }, + "TitledSingleSelectEnumSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\TitledSingleSelectEnumSchema", + "purpose": "Schema for single-selection enumeration with display titles for each option", + "implements": [ + "SingleSelectEnumSchemaInterface" + ], + "properties": { + "type": "\"string\"", + "title": "string?", + "description": "string?", + "oneOf": "Array<{\n /**\n * The enum value.\n */\n...", + "default": "string?" + }, + "usedBy": [ + "SingleSelectEnumSchemaInterface" + ], + "uses": [ + "Array<{\n /**\n * The enum value.\n */\n const: string;\n /**\n * Display label for this option.\n */\n title: string;\n }>" + ], + "discriminator": { + "field": "type", + "value": "string" + } + }, + "UntitledMultiSelectEnumSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\UntitledMultiSelectEnumSchema", + "purpose": "Schema for multiple-selection enumeration without display titles for options", + "implements": [ + "MultiSelectEnumSchemaInterface" + ], + "properties": { + "type": "\"array\"", + "title": "string?", + "description": "string?", + "minItems": "number?", + "maxItems": "number?", + "items": "UntitledMultiSelectEnumSchemaItems", + "default": "string[]?" + }, + "usedBy": [ + "MultiSelectEnumSchemaInterface" + ], + "uses": [ + "UntitledMultiSelectEnumSchemaItems" + ], + "discriminator": { + "field": "type", + "value": "array" + } + }, + "TitledMultiSelectEnumSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\TitledMultiSelectEnumSchema", + "purpose": "Schema for multiple-selection enumeration with display titles for each option", + "implements": [ + "MultiSelectEnumSchemaInterface" + ], + "properties": { + "type": "\"array\"", + "title": "string?", + "description": "string?", + "minItems": "number?", + "maxItems": "number?", + "items": "TitledMultiSelectEnumSchemaItems", + "default": "string[]?" + }, + "usedBy": [ + "MultiSelectEnumSchemaInterface" + ], + "uses": [ + "TitledMultiSelectEnumSchemaItems" + ], + "discriminator": { + "field": "type", + "value": "array" + } + }, + "LegacyTitledEnumSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\LegacyTitledEnumSchema", + "purpose": "Use TitledSingleSelectEnumSchema instead", + "implements": [ + "EnumSchemaInterface" + ], + "properties": { + "type": "\"string\"", + "title": "string?", + "description": "string?", + "enum": "string[]", + "enumNames": "string[]?", + "default": "string?" + }, + "usedBy": [ + "EnumSchemaInterface" + ], + "uses": [], + "discriminator": { + "field": "type", + "value": "string" + } + }, + "ElicitResult": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitResult", + "purpose": "The client's response to an elicitation request", + "extends": "Result", + "implements": [ + "ClientResultInterface" + ], + "properties": { + "action": "\"accept\" | \"decline\" | \"cancel\"", + "content": "{ [key: string]: string | number | boolean | st...?" + }, + "usedBy": [ + "ClientResultInterface" + ], + "uses": [ + "Result" + ] + }, + "ElicitationCompleteNotification": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitationCompleteNotification", + "purpose": "An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/elicitation/complete\"", + "params": "ElicitationCompleteNotificationParams" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "ElicitationCompleteNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/elicitation/complete" + } + }, + "ClientCapabilitiesRoots": { + "kind": "class", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\ClientCapabilitiesRoots", + "purpose": "Present if the client supports listing roots", + "implements": [], + "properties": { + "listChanged": "boolean?" + }, + "usedBy": [ + "ClientCapabilities" + ], + "uses": [] + }, + "ClientCapabilitiesSampling": { + "kind": "class", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\ClientCapabilitiesSampling", + "purpose": "Present if the client supports sampling from an LLM", + "implements": [], + "properties": { + "context": "object?", + "tools": "object?" + }, + "usedBy": [ + "ClientCapabilities" + ], + "uses": [] + }, + "ClientCapabilitiesElicitation": { + "kind": "class", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\ClientCapabilitiesElicitation", + "purpose": "Present if the client supports elicitation from the server", + "implements": [], + "properties": { + "form": "object?", + "url": "object?" + }, + "usedBy": [ + "ClientCapabilities" + ], + "uses": [] + }, + "ClientCapabilitiesTasks": { + "kind": "class", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\ClientCapabilitiesTasks", + "purpose": "Present if the client supports task-augmented requests", + "implements": [], + "properties": { + "list": "object?", + "cancel": "object?" + }, + "usedBy": [ + "ClientCapabilities" + ], + "uses": [] + }, + "ElicitRequestFormParamsRequestedSchema": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitRequestFormParamsRequestedSchema", + "purpose": "A restricted subset of JSON Schema", + "implements": [], + "properties": { + "$schema": "string?", + "type": "\"object\"", + "required": "string[]?" + }, + "usedBy": [ + "ElicitRequestFormParams" + ], + "uses": [] + }, + "UntitledMultiSelectEnumSchemaItems": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\UntitledMultiSelectEnumSchemaItems", + "purpose": "Schema for the array items", + "implements": [], + "properties": { + "type": "\"string\"", + "enum": "string[]" + }, + "usedBy": [ + "UntitledMultiSelectEnumSchema" + ], + "uses": [] + }, + "TitledMultiSelectEnumSchemaItems": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\TitledMultiSelectEnumSchemaItems", + "purpose": "Schema for array items with enum options and display labels", + "implements": [], + "properties": {}, + "usedBy": [ + "TitledMultiSelectEnumSchema" + ], + "uses": [] + }, + "ElicitationCompleteNotificationParams": { + "kind": "class", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\ElicitationCompleteNotificationParams", + "purpose": "Parameters for ElicitationCompleteNotification", + "implements": [], + "properties": { + "elicitationId": "string" + }, + "usedBy": [ + "ElicitationCompleteNotification" + ], + "uses": [] + }, + "TaskStatusInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Client\\Tasks\\Union\\TaskStatusInterface", + "purpose": "The status of a task.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [] + }, + "ElicitRequestParamsInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\Union\\ElicitRequestParamsInterface", + "purpose": "The parameters for a request to elicit additional information from the user via the client.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "ElicitRequestFormParams", + "ElicitRequestURLParams" + ] + }, + "PrimitiveSchemaDefinitionInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\Union\\PrimitiveSchemaDefinitionInterface", + "purpose": "Restricted schema definitions that only allow primitive types\nwithout nested objects or arrays.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "StringSchema", + "NumberSchema", + "BooleanSchema", + "EnumSchema" + ] + }, + "SingleSelectEnumSchemaInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\Union\\SingleSelectEnumSchemaInterface", + "purpose": "Union type: UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "UntitledSingleSelectEnumSchema", + "TitledSingleSelectEnumSchema" + ] + }, + "MultiSelectEnumSchemaInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\Union\\MultiSelectEnumSchemaInterface", + "purpose": "Union type: UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "UntitledMultiSelectEnumSchema", + "TitledMultiSelectEnumSchema" + ] + }, + "EnumSchemaInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Elicitation", + "namespace": "WP\\McpSchema\\Client\\Elicitation\\Union\\EnumSchemaInterface", + "purpose": "Union type: SingleSelectEnumSchema | MultiSelectEnumSchema | LegacyTitledEnumSchema", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "SingleSelectEnumSchema", + "MultiSelectEnumSchema", + "LegacyTitledEnumSchema" + ] + }, + "ClientResultInterface": { + "kind": "union", + "domain": "Client", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Client\\Lifecycle\\Union\\ClientResultInterface", + "purpose": "Union type: EmptyResult | CreateMessageResult | ListRootsResult | ElicitResult | GetTaskResult | GetTaskPayloadResult | ListTasksResult | CancelTaskResult", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "EmptyResult", + "CreateMessageResult", + "ListRootsResult", + "ElicitResult", + "GetTaskResult", + "GetTaskPayloadResult", + "ListTasksResult", + "CancelTaskResult" + ] + } + }, + "factories": { + "ElicitRequestParamsFactory": { + "interface": "ElicitRequestParamsInterface", + "discriminator": "mode", + "mappings": { + "form": "ElicitRequestFormParams", + "url": "ElicitRequestURLParams" + } + }, + "PrimitiveSchemaDefinitionFactory": { + "interface": "PrimitiveSchemaDefinitionInterface", + "discriminator": "type", + "mappings": { + "string": "StringSchema", + "number\" | \"integer": "NumberSchema", + "boolean": "BooleanSchema" + } + }, + "SingleSelectEnumSchemaFactory": { + "interface": "SingleSelectEnumSchemaInterface", + "discriminator": "type", + "mappings": { + "string": "TitledSingleSelectEnumSchema" + } + }, + "MultiSelectEnumSchemaFactory": { + "interface": "MultiSelectEnumSchemaInterface", + "discriminator": "type", + "mappings": { + "array": "TitledMultiSelectEnumSchema" + } + }, + "EnumSchemaFactory": { + "interface": "EnumSchemaInterface", + "discriminator": "type", + "mappings": { + "string": "LegacyTitledEnumSchema" + } + } + } +} \ No newline at end of file diff --git a/skill/data/schema-common.json b/skill/data/schema-common.json new file mode 100644 index 0000000..f2d64d9 --- /dev/null +++ b/skill/data/schema-common.json @@ -0,0 +1,1393 @@ +{ + "version": "2025-11-25", + "domain": "Common", + "types": { + "TaskAugmentedRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\TaskAugmentedRequestParams", + "purpose": "Common params for any task-augmented request", + "extends": "RequestParams", + "implements": [], + "properties": { + "task": "TaskMetadata?" + }, + "usedBy": [ + "CallToolRequestParams", + "CreateMessageRequestParams", + "ElicitRequestFormParams", + "ElicitRequestURLParams" + ], + "uses": [ + "RequestParams", + "TaskMetadata" + ] + }, + "RequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\RequestParams", + "purpose": "Common params for any request", + "implements": [], + "properties": { + "_meta": "RequestParamsMeta | undefined?" + }, + "usedBy": [ + "TaskAugmentedRequestParams", + "InitializeRequestParams", + "PingRequest", + "PaginatedRequestParams", + "ResourceRequestParams", + "GetPromptRequestParams", + "SetLevelRequestParams", + "CompleteRequestParams", + "ListRootsRequest" + ], + "uses": [ + "RequestParamsMeta" + ] + }, + "Request": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Request", + "purpose": "Request for operation", + "implements": [], + "properties": { + "method": "string", + "params": "{ [key: string]: any }?" + }, + "usedBy": [ + "JSONRPCRequest" + ], + "uses": [] + }, + "NotificationParams": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\NotificationParams", + "purpose": "Parameters for Notification", + "implements": [], + "properties": { + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "CancelledNotificationParams", + "InitializedNotification", + "ProgressNotificationParams", + "ResourceListChangedNotification", + "ResourceUpdatedNotificationParams", + "PromptListChangedNotification", + "ToolListChangedNotification", + "LoggingMessageNotificationParams", + "RootsListChangedNotification" + ], + "uses": [] + }, + "Notification": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Notification", + "purpose": "Notification for events", + "implements": [], + "properties": { + "method": "string", + "params": "{ [key: string]: any }?" + }, + "usedBy": [ + "JSONRPCNotification" + ], + "uses": [] + }, + "Result": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Result", + "purpose": "Result from operation", + "implements": [], + "properties": { + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "JSONRPCResultResponse", + "InitializeResult", + "PaginatedResult", + "ReadResourceResult", + "GetPromptResult", + "CallToolResult", + "CreateTaskResult", + "GetTaskPayloadResult", + "CreateMessageResult", + "CompleteResult", + "ListRootsResult", + "ElicitResult", + "EmptyResult", + "GetTaskResult", + "CancelTaskResult" + ], + "uses": [] + }, + "Error": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Error", + "purpose": "Error data structure", + "implements": [], + "properties": { + "code": "number", + "message": "string", + "data": "unknown?" + }, + "usedBy": [ + "JSONRPCErrorResponse" + ], + "uses": [] + }, + "JSONRPCRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\JSONRPCRequest", + "purpose": "A request that expects a response", + "extends": "Request", + "implements": [ + "JSONRPCMessageInterface" + ], + "properties": { + "jsonrpc": "typeof JSONRPC_VERSION", + "id": "RequestId" + }, + "usedBy": [ + "InitializeRequest", + "PingRequest", + "PaginatedRequest", + "ReadResourceRequest", + "SubscribeRequest", + "UnsubscribeRequest", + "GetPromptRequest", + "CallToolRequest", + "GetTaskRequest", + "GetTaskPayloadRequest", + "CancelTaskRequest", + "SetLevelRequest", + "CreateMessageRequest", + "CompleteRequest", + "ListRootsRequest", + "ElicitRequest", + "JSONRPCMessageInterface" + ], + "uses": [ + "Request", + "RequestId" + ] + }, + "JSONRPCNotification": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\JSONRPCNotification", + "purpose": "A notification which does not expect a response", + "extends": "Notification", + "implements": [ + "JSONRPCMessageInterface" + ], + "properties": { + "jsonrpc": "typeof JSONRPC_VERSION" + }, + "usedBy": [ + "CancelledNotification", + "InitializedNotification", + "ProgressNotification", + "ResourceListChangedNotification", + "ResourceUpdatedNotification", + "PromptListChangedNotification", + "ToolListChangedNotification", + "TaskStatusNotification", + "LoggingMessageNotification", + "RootsListChangedNotification", + "ElicitationCompleteNotification", + "JSONRPCMessageInterface" + ], + "uses": [ + "Notification" + ] + }, + "JSONRPCResultResponse": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\JSONRPCResultResponse", + "purpose": "A successful (non-error) response to a request", + "implements": [ + "JSONRPCResponseInterface" + ], + "properties": { + "jsonrpc": "typeof JSONRPC_VERSION", + "id": "RequestId", + "result": "Result" + }, + "usedBy": [ + "JSONRPCResponseInterface" + ], + "uses": [ + "RequestId", + "Result" + ] + }, + "JSONRPCErrorResponse": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\JSONRPCErrorResponse", + "purpose": "A response to a request that indicates an error occurred", + "implements": [ + "JSONRPCResponseInterface" + ], + "properties": { + "jsonrpc": "typeof JSONRPC_VERSION", + "id": "RequestId?", + "error": "Error" + }, + "usedBy": [ + "JSONRPCResponseInterface" + ], + "uses": [ + "RequestId", + "Error" + ] + }, + "URLElicitationRequiredError": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\URLElicitationRequiredError", + "purpose": "An error response that indicates that the server requires the client to provide additional information via an elicitation request", + "extends": "Omit", + "implements": [], + "properties": { + "error": "Error & {\n code: typeof URL_ELICITATION_REQU..." + }, + "usedBy": [], + "uses": [ + "Omit", + "Error & {\n code: typeof URL_ELICITATION_REQUIRED;\n data: {\n elicitations: ElicitRequestURLParams[];\n [key: string]: unknown;\n };\n }" + ] + }, + "CancelledNotificationParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\CancelledNotificationParams", + "purpose": "Parameters for a `notifications/cancelled` notification", + "extends": "NotificationParams", + "implements": [], + "properties": { + "requestId": "RequestId?", + "reason": "string?" + }, + "usedBy": [ + "CancelledNotification" + ], + "uses": [ + "NotificationParams", + "RequestId" + ] + }, + "CancelledNotification": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\CancelledNotification", + "purpose": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request", + "extends": "JSONRPCNotification", + "implements": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/cancelled\"", + "params": "CancelledNotificationParams" + }, + "usedBy": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "CancelledNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/cancelled" + } + }, + "InitializeRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\InitializeRequestParams", + "purpose": "Parameters for an `initialize` request", + "extends": "RequestParams", + "implements": [], + "properties": { + "protocolVersion": "string", + "capabilities": "ClientCapabilities", + "clientInfo": "Implementation" + }, + "usedBy": [ + "InitializeRequest" + ], + "uses": [ + "RequestParams", + "ClientCapabilities", + "Implementation" + ] + }, + "InitializeRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\InitializeRequest", + "purpose": "This request is sent from the client to the server when it first connects, asking it to begin initialization", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"initialize\"", + "params": "InitializeRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "InitializeRequestParams" + ], + "discriminator": { + "field": "method", + "value": "initialize" + } + }, + "InitializeResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\InitializeResult", + "purpose": "After receiving an initialize request from the client, the server sends this response", + "extends": "Result", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "protocolVersion": "string", + "capabilities": "ServerCapabilities", + "serverInfo": "Implementation", + "instructions": "string?" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "Result", + "ServerCapabilities", + "Implementation" + ] + }, + "InitializedNotification": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\InitializedNotification", + "purpose": "This notification is sent from the client to the server after initialization has finished", + "extends": "JSONRPCNotification", + "implements": [ + "ClientNotificationInterface" + ], + "properties": { + "method": "\"notifications/initialized\"", + "params": "NotificationParams?" + }, + "usedBy": [ + "ClientNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "NotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/initialized" + } + }, + "Icon": { + "kind": "class", + "domain": "Common", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Common\\Core\\Icon", + "purpose": "An optionally-sized icon that can be displayed in a user interface", + "implements": [], + "properties": { + "src": "string", + "mimeType": "string?", + "sizes": "string[]?", + "theme": "\"light\" | \"dark\"?" + }, + "usedBy": [ + "Icons" + ], + "uses": [] + }, + "Icons": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Icons", + "purpose": "Base interface to add `icons` property", + "implements": [], + "properties": { + "icons": "Icon[]?" + }, + "usedBy": [ + "Implementation", + "Resource", + "ResourceTemplate", + "Prompt", + "Tool" + ], + "uses": [ + "Icon" + ] + }, + "BaseMetadata": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\BaseMetadata", + "purpose": "Base interface for metadata with name (identifier) and title (display name) properties", + "implements": [], + "properties": { + "name": "string", + "title": "string?" + }, + "usedBy": [ + "Implementation", + "Resource", + "ResourceTemplate", + "Prompt", + "PromptArgument", + "Tool", + "PromptReference" + ], + "uses": [] + }, + "Implementation": { + "kind": "class", + "domain": "Common", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Common\\Lifecycle\\Implementation", + "purpose": "Describes the MCP implementation", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "version": "string", + "description": "string?", + "websiteUrl": "string?" + }, + "usedBy": [ + "InitializeRequestParams", + "InitializeResult" + ], + "uses": [ + "BaseMetadata", + "Icons" + ] + }, + "PingRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\PingRequest", + "purpose": "A ping, issued by either the server or the client, to check that the other party is still alive", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "properties": { + "method": "\"ping\"", + "params": "RequestParams?" + }, + "usedBy": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "RequestParams" + ], + "discriminator": { + "field": "method", + "value": "ping" + } + }, + "ProgressNotificationParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\ProgressNotificationParams", + "purpose": "Parameters for a `notifications/progress` notification", + "extends": "NotificationParams", + "implements": [], + "properties": { + "progressToken": "ProgressToken", + "progress": "number", + "total": "number?", + "message": "string?" + }, + "usedBy": [ + "ProgressNotification" + ], + "uses": [ + "NotificationParams", + "ProgressToken" + ] + }, + "ProgressNotification": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\ProgressNotification", + "purpose": "An out-of-band notification used to inform the receiver of a progress update for a long-running request", + "extends": "JSONRPCNotification", + "implements": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/progress\"", + "params": "ProgressNotificationParams" + }, + "usedBy": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "ProgressNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/progress" + } + }, + "PaginatedRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\PaginatedRequestParams", + "purpose": "Common parameters for paginated requests", + "extends": "RequestParams", + "implements": [], + "properties": { + "cursor": "Cursor?" + }, + "usedBy": [ + "PaginatedRequest" + ], + "uses": [ + "RequestParams", + "Cursor" + ] + }, + "PaginatedRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\PaginatedRequest", + "purpose": "Request for Paginated operation", + "extends": "JSONRPCRequest", + "implements": [], + "properties": { + "params": "PaginatedRequestParams?" + }, + "usedBy": [ + "ListResourcesRequest", + "ListResourceTemplatesRequest", + "ListPromptsRequest", + "ListToolsRequest", + "ListTasksRequest" + ], + "uses": [ + "JSONRPCRequest", + "PaginatedRequestParams" + ] + }, + "PaginatedResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\PaginatedResult", + "purpose": "Result from Paginated operation", + "extends": "Result", + "implements": [], + "properties": { + "nextCursor": "Cursor?" + }, + "usedBy": [ + "ListResourcesResult", + "ListResourceTemplatesResult", + "ListPromptsResult", + "ListToolsResult", + "ListTasksResult" + ], + "uses": [ + "Result", + "Cursor" + ] + }, + "TextResourceContents": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\TextResourceContents", + "purpose": "Text Resource Contents data structure", + "extends": "ResourceContents", + "implements": [], + "properties": { + "text": "string" + }, + "usedBy": [ + "EmbeddedResource" + ], + "uses": [ + "ResourceContents" + ] + }, + "BlobResourceContents": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\BlobResourceContents", + "purpose": "Blob Resource Contents data structure", + "extends": "ResourceContents", + "implements": [], + "properties": { + "blob": "string" + }, + "usedBy": [ + "EmbeddedResource" + ], + "uses": [ + "ResourceContents" + ] + }, + "EmbeddedResource": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\EmbeddedResource", + "purpose": "The contents of a resource, embedded into a prompt or tool call result", + "implements": [ + "ContentBlockInterface" + ], + "properties": { + "type": "\"resource\"", + "resource": "TextResourceContents | BlobResourceContents", + "annotations": "Annotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ContentBlockInterface" + ], + "uses": [ + "TextResourceContents", + "BlobResourceContents", + "Annotations" + ], + "discriminator": { + "field": "type", + "value": "resource" + } + }, + "GetTaskRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\GetTaskRequest", + "purpose": "A request to retrieve the state of a task", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "properties": { + "method": "\"tasks/get\"", + "params": "GetTaskRequestParams" + }, + "usedBy": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "GetTaskRequestParams" + ], + "discriminator": { + "field": "method", + "value": "tasks/get" + } + }, + "GetTaskPayloadRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\GetTaskPayloadRequest", + "purpose": "A request to retrieve the result of a completed task", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "properties": { + "method": "\"tasks/result\"", + "params": "GetTaskPayloadRequestParams" + }, + "usedBy": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "GetTaskPayloadRequestParams" + ], + "discriminator": { + "field": "method", + "value": "tasks/result" + } + }, + "GetTaskPayloadResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\GetTaskPayloadResult", + "purpose": "The response to a tasks/result request", + "extends": "Result", + "implements": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "properties": {}, + "usedBy": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "uses": [ + "Result" + ] + }, + "CancelTaskRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\CancelTaskRequest", + "purpose": "A request to cancel a task", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "properties": { + "method": "\"tasks/cancel\"", + "params": "CancelTaskRequestParams" + }, + "usedBy": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "CancelTaskRequestParams" + ], + "discriminator": { + "field": "method", + "value": "tasks/cancel" + } + }, + "ListTasksRequest": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\ListTasksRequest", + "purpose": "A request to retrieve a list of tasks", + "extends": "PaginatedRequest", + "implements": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "properties": { + "method": "\"tasks/list\"" + }, + "usedBy": [ + "ClientRequestInterface", + "ServerRequestInterface" + ], + "uses": [ + "PaginatedRequest" + ], + "discriminator": { + "field": "method", + "value": "tasks/list" + } + }, + "ListTasksResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\ListTasksResult", + "purpose": "The response to a tasks/list request", + "extends": "PaginatedResult", + "implements": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "properties": { + "tasks": "Task[]" + }, + "usedBy": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "uses": [ + "PaginatedResult", + "Task" + ] + }, + "TaskStatusNotification": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\TaskStatusNotification", + "purpose": "An optional notification from the receiver to the requestor, informing them that a task's status has changed", + "extends": "JSONRPCNotification", + "implements": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/tasks/status\"", + "params": "TaskStatusNotificationParams" + }, + "usedBy": [ + "ClientNotificationInterface", + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "TaskStatusNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/tasks/status" + } + }, + "Annotations": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Annotations", + "purpose": "Optional annotations for the client", + "implements": [], + "properties": { + "audience": "Role[]?", + "priority": "number?", + "lastModified": "string?" + }, + "usedBy": [ + "Resource", + "ResourceTemplate", + "EmbeddedResource", + "TextContent", + "ImageContent", + "AudioContent" + ], + "uses": [ + "Role" + ] + }, + "TextContent": { + "kind": "class", + "domain": "Common", + "subdomain": "Content", + "namespace": "WP\\McpSchema\\Common\\Content\\TextContent", + "purpose": "Text provided to or from an LLM", + "implements": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "properties": { + "type": "\"text\"", + "text": "string", + "annotations": "Annotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "uses": [ + "Annotations" + ], + "discriminator": { + "field": "type", + "value": "text" + } + }, + "ImageContent": { + "kind": "class", + "domain": "Common", + "subdomain": "Content", + "namespace": "WP\\McpSchema\\Common\\Content\\ImageContent", + "purpose": "An image provided to or from an LLM", + "implements": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "properties": { + "type": "\"image\"", + "data": "string", + "mimeType": "string", + "annotations": "Annotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "uses": [ + "Annotations" + ], + "discriminator": { + "field": "type", + "value": "image" + } + }, + "AudioContent": { + "kind": "class", + "domain": "Common", + "subdomain": "Content", + "namespace": "WP\\McpSchema\\Common\\Content\\AudioContent", + "purpose": "Audio provided to or from an LLM", + "implements": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "properties": { + "type": "\"audio\"", + "data": "string", + "mimeType": "string", + "annotations": "Annotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "SamplingMessageContentBlockInterface", + "ContentBlockInterface" + ], + "uses": [ + "Annotations" + ], + "discriminator": { + "field": "type", + "value": "audio" + } + }, + "RequestParamsMeta": { + "kind": "class", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\RequestParamsMeta", + "purpose": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage", + "implements": [], + "properties": { + "progressToken": "ProgressToken?" + }, + "usedBy": [ + "RequestParams" + ], + "uses": [ + "ProgressToken" + ] + }, + "GetTaskRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\GetTaskRequestParams", + "purpose": "Parameters for GetTaskRequest", + "implements": [], + "properties": { + "taskId": "string" + }, + "usedBy": [ + "GetTaskRequest" + ], + "uses": [] + }, + "GetTaskPayloadRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\GetTaskPayloadRequestParams", + "purpose": "Parameters for GetTaskPayloadRequest", + "implements": [], + "properties": { + "taskId": "string" + }, + "usedBy": [ + "GetTaskPayloadRequest" + ], + "uses": [] + }, + "CancelTaskRequestParams": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\CancelTaskRequestParams", + "purpose": "Parameters for CancelTaskRequest", + "implements": [], + "properties": { + "taskId": "string" + }, + "usedBy": [ + "CancelTaskRequest" + ], + "uses": [] + }, + "JSONRPCMessageInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Union\\JSONRPCMessageInterface", + "purpose": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "JSONRPCRequest", + "JSONRPCNotification", + "JSONRPCResponse" + ] + }, + "ProgressTokenInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\ProgressTokenInterface", + "purpose": "A progress token, used to associate progress notifications with the original request.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [] + }, + "RequestIdInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Union\\RequestIdInterface", + "purpose": "A uniquely identifying ID for a request in JSON-RPC.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [] + }, + "JSONRPCResponseInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "JsonRpc", + "namespace": "WP\\McpSchema\\Common\\JsonRpc\\Union\\JSONRPCResponseInterface", + "purpose": "A response to a request, containing either the result or error.", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "JSONRPCResultResponse", + "JSONRPCErrorResponse" + ] + }, + "EmptyResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\EmptyResult", + "purpose": "A response that indicates success but carries no data.", + "extends": "Result", + "implements": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "properties": {}, + "usedBy": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "uses": [ + "Result" + ] + }, + "Role": { + "kind": "enum", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Enum\\Role", + "purpose": "The sender or recipient of messages and data in a conversation.", + "implements": [], + "properties": { + "USER": "user", + "ASSISTANT": "assistant" + }, + "usedBy": [ + "PromptMessage", + "SamplingMessage", + "Annotations" + ], + "uses": [] + }, + "SamplingMessageContentBlockInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\SamplingMessageContentBlockInterface", + "purpose": "Union type: TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "TextContent", + "ImageContent", + "AudioContent", + "ToolUseContent", + "ToolResultContent" + ] + }, + "ContentBlockInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\ContentBlockInterface", + "purpose": "Union type: TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "TextContent", + "ImageContent", + "AudioContent", + "ResourceLink", + "EmbeddedResource" + ] + }, + "ClientRequestInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\ClientRequestInterface", + "purpose": "Union type: PingRequest | InitializeRequest | CompleteRequest | SetLevelRequest | GetPromptRequest | ListPromptsRequest | ListResourcesRequest | ListResourceTemplatesRequest | ReadResourceRequest | SubscribeRequest | UnsubscribeRequest | CallToolRequest | ListToolsRequest | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest | CancelTaskRequest", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "PingRequest", + "InitializeRequest", + "CompleteRequest", + "SetLevelRequest", + "GetPromptRequest", + "ListPromptsRequest", + "ListResourcesRequest", + "ListResourceTemplatesRequest", + "ReadResourceRequest", + "SubscribeRequest", + "UnsubscribeRequest", + "CallToolRequest", + "ListToolsRequest", + "GetTaskRequest", + "GetTaskPayloadRequest", + "ListTasksRequest", + "CancelTaskRequest" + ] + }, + "ClientNotificationInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\ClientNotificationInterface", + "purpose": "Union type: CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification | TaskStatusNotification", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "CancelledNotification", + "ProgressNotification", + "InitializedNotification", + "RootsListChangedNotification", + "TaskStatusNotification" + ] + }, + "ServerRequestInterface": { + "kind": "union", + "domain": "Common", + "subdomain": "Protocol", + "namespace": "WP\\McpSchema\\Common\\Protocol\\Union\\ServerRequestInterface", + "purpose": "Union type: PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest | CancelTaskRequest", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "PingRequest", + "CreateMessageRequest", + "ListRootsRequest", + "ElicitRequest", + "GetTaskRequest", + "GetTaskPayloadRequest", + "ListTasksRequest", + "CancelTaskRequest" + ] + }, + "GetTaskResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\GetTaskResult", + "purpose": "The response to a tasks/get request.", + "extends": "Result", + "implements": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "properties": { + "taskId": "string", + "status": "\"working\" | \"input_required\" | \"completed\" | \"f...", + "statusMessage": "string?", + "createdAt": "string", + "lastUpdatedAt": "string", + "ttl": "number | null", + "pollInterval": "number?" + }, + "usedBy": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "uses": [ + "Result", + "Task" + ] + }, + "CancelTaskResult": { + "kind": "class", + "domain": "Common", + "subdomain": "Tasks", + "namespace": "WP\\McpSchema\\Common\\Tasks\\CancelTaskResult", + "purpose": "The response to a tasks/cancel request.", + "extends": "Result", + "implements": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "properties": { + "taskId": "string", + "status": "\"working\" | \"input_required\" | \"completed\" | \"f...", + "statusMessage": "string?", + "createdAt": "string", + "lastUpdatedAt": "string", + "ttl": "number | null", + "pollInterval": "number?" + }, + "usedBy": [ + "ClientResultInterface", + "ServerResultInterface" + ], + "uses": [ + "Result", + "Task" + ] + } + }, + "factories": { + "SamplingMessageContentBlockFactory": { + "interface": "SamplingMessageContentBlockInterface", + "discriminator": "type", + "mappings": { + "text": "TextContent", + "image": "ImageContent", + "audio": "AudioContent", + "tool_use": "ToolUseContent", + "tool_result": "ToolResultContent" + } + }, + "ContentBlockFactory": { + "interface": "ContentBlockInterface", + "discriminator": "type", + "mappings": { + "text": "TextContent", + "image": "ImageContent", + "audio": "AudioContent", + "resource_link": "ResourceLink", + "resource": "EmbeddedResource" + } + }, + "ClientRequestFactory": { + "interface": "ClientRequestInterface", + "discriminator": "method", + "mappings": { + "ping": "PingRequest", + "initialize": "InitializeRequest", + "completion/complete": "CompleteRequest", + "logging/setLevel": "SetLevelRequest", + "prompts/get": "GetPromptRequest", + "prompts/list": "ListPromptsRequest", + "resources/list": "ListResourcesRequest", + "resources/templates/list": "ListResourceTemplatesRequest", + "resources/read": "ReadResourceRequest", + "resources/subscribe": "SubscribeRequest", + "resources/unsubscribe": "UnsubscribeRequest", + "tools/call": "CallToolRequest", + "tools/list": "ListToolsRequest", + "tasks/get": "GetTaskRequest", + "tasks/result": "GetTaskPayloadRequest", + "tasks/list": "ListTasksRequest", + "tasks/cancel": "CancelTaskRequest" + } + }, + "ClientNotificationFactory": { + "interface": "ClientNotificationInterface", + "discriminator": "method", + "mappings": { + "notifications/cancelled": "CancelledNotification", + "notifications/progress": "ProgressNotification", + "notifications/initialized": "InitializedNotification", + "notifications/roots/list_changed": "RootsListChangedNotification", + "notifications/tasks/status": "TaskStatusNotification" + } + }, + "ServerRequestFactory": { + "interface": "ServerRequestInterface", + "discriminator": "method", + "mappings": { + "ping": "PingRequest", + "sampling/createMessage": "CreateMessageRequest", + "roots/list": "ListRootsRequest", + "elicitation/create": "ElicitRequest", + "tasks/get": "GetTaskRequest", + "tasks/result": "GetTaskPayloadRequest", + "tasks/list": "ListTasksRequest", + "tasks/cancel": "CancelTaskRequest" + } + } + } +} \ No newline at end of file diff --git a/skill/data/schema-index.json b/skill/data/schema-index.json new file mode 100644 index 0000000..aa98210 --- /dev/null +++ b/skill/data/schema-index.json @@ -0,0 +1,130 @@ +{ + "version": "2025-11-25", + "namespace": "WP\\McpSchema", + "summary": { + "types": 164, + "domains": 3, + "subdomains": 17, + "rpcMethods": 20, + "factories": 11 + }, + "domains": { + "Common": [ + "Tasks", + "JsonRpc", + "Protocol", + "Core", + "Lifecycle", + "Content" + ], + "Client": [ + "Lifecycle", + "Tasks", + "Sampling", + "Roots", + "Elicitation" + ], + "Server": [ + "Lifecycle", + "Resources", + "Prompts", + "Tools", + "Logging", + "Core" + ] + }, + "rpcMethods": [ + { + "method": "initialize", + "direction": "client→server" + }, + { + "method": "ping", + "direction": "bidirectional" + }, + { + "method": "resources/list", + "direction": "client→server" + }, + { + "method": "resources/templates/list", + "direction": "client→server" + }, + { + "method": "resources/read", + "direction": "client→server" + }, + { + "method": "resources/subscribe", + "direction": "client→server" + }, + { + "method": "resources/unsubscribe", + "direction": "client→server" + }, + { + "method": "prompts/list", + "direction": "client→server" + }, + { + "method": "prompts/get", + "direction": "client→server" + }, + { + "method": "tools/list", + "direction": "client→server" + }, + { + "method": "tools/call", + "direction": "client→server" + }, + { + "method": "tasks/get", + "direction": "bidirectional" + }, + { + "method": "tasks/result", + "direction": "bidirectional" + }, + { + "method": "tasks/cancel", + "direction": "bidirectional" + }, + { + "method": "tasks/list", + "direction": "bidirectional" + }, + { + "method": "logging/setLevel", + "direction": "client→server" + }, + { + "method": "sampling/createMessage", + "direction": "server→client" + }, + { + "method": "completion/complete", + "direction": "client→server" + }, + { + "method": "roots/list", + "direction": "server→client" + }, + { + "method": "elicitation/create", + "direction": "server→client" + } + ], + "entryPoints": { + "Common/Tasks": "tasks/get", + "Common/Protocol": "initialize", + "Server/Resources": "resources/list", + "Server/Prompts": "prompts/list", + "Server/Tools": "tools/list", + "Server/Logging": "logging/setLevel", + "Client/Sampling": "sampling/createMessage", + "Server/Core": "completion/complete", + "Client/Roots": "roots/list", + "Client/Elicitation": "elicitation/create" + } +} \ No newline at end of file diff --git a/skill/data/schema-server.json b/skill/data/schema-server.json new file mode 100644 index 0000000..2408ec6 --- /dev/null +++ b/skill/data/schema-server.json @@ -0,0 +1,1269 @@ +{ + "version": "2025-11-25", + "domain": "Server", + "types": { + "ServerCapabilities": { + "kind": "class", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\ServerCapabilities", + "purpose": "Capabilities that a server may support", + "implements": [], + "properties": { + "experimental": "{ [key: string]: object }?", + "logging": "object?", + "completions": "object?", + "prompts": "ServerCapabilitiesPrompts | undefined?", + "resources": "ServerCapabilitiesResources | undefined?", + "tools": "ServerCapabilitiesTools | undefined?", + "tasks": "ServerCapabilitiesTasks | undefined?" + }, + "usedBy": [ + "InitializeResult" + ], + "uses": [ + "ServerCapabilitiesPrompts", + "ServerCapabilitiesResources", + "ServerCapabilitiesTools", + "ServerCapabilitiesTasks" + ] + }, + "ListResourcesRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ListResourcesRequest", + "purpose": "Sent from the client to request a list of resources the server has", + "extends": "PaginatedRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"resources/list\"" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "PaginatedRequest" + ], + "discriminator": { + "field": "method", + "value": "resources/list" + } + }, + "ListResourcesResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ListResourcesResult", + "purpose": "The server's response to a resources/list request from the client", + "extends": "PaginatedResult", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "resources": "Resource[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "PaginatedResult", + "Resource" + ] + }, + "ListResourceTemplatesRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ListResourceTemplatesRequest", + "purpose": "Sent from the client to request a list of resource templates the server has", + "extends": "PaginatedRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"resources/templates/list\"" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "PaginatedRequest" + ], + "discriminator": { + "field": "method", + "value": "resources/templates/list" + } + }, + "ListResourceTemplatesResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ListResourceTemplatesResult", + "purpose": "The server's response to a resources/templates/list request from the client", + "extends": "PaginatedResult", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "resourceTemplates": "ResourceTemplate[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "PaginatedResult", + "ResourceTemplate" + ] + }, + "ResourceRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceRequestParams", + "purpose": "Common parameters when working with resources", + "extends": "RequestParams", + "implements": [], + "properties": { + "uri": "string" + }, + "usedBy": [ + "ReadResourceRequestParams", + "SubscribeRequestParams", + "UnsubscribeRequestParams" + ], + "uses": [ + "RequestParams" + ] + }, + "ReadResourceRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ReadResourceRequestParams", + "purpose": "Parameters for a `resources/read` request", + "extends": "ResourceRequestParams", + "implements": [], + "properties": {}, + "usedBy": [ + "ReadResourceRequest" + ], + "uses": [ + "ResourceRequestParams" + ] + }, + "ReadResourceRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ReadResourceRequest", + "purpose": "Sent from the client to the server, to read a specific resource URI", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"resources/read\"", + "params": "ReadResourceRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "ReadResourceRequestParams" + ], + "discriminator": { + "field": "method", + "value": "resources/read" + } + }, + "ReadResourceResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ReadResourceResult", + "purpose": "The server's response to a resources/read request from the client", + "extends": "Result", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "contents": "(TextResourceContents | BlobResourceContents)[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "Result", + "BlobResourceContents)" + ] + }, + "ResourceListChangedNotification": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceListChangedNotification", + "purpose": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/resources/list_changed\"", + "params": "NotificationParams?" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "NotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/resources/list_changed" + } + }, + "SubscribeRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\SubscribeRequestParams", + "purpose": "Parameters for a `resources/subscribe` request", + "extends": "ResourceRequestParams", + "implements": [], + "properties": {}, + "usedBy": [ + "SubscribeRequest" + ], + "uses": [ + "ResourceRequestParams" + ] + }, + "SubscribeRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\SubscribeRequest", + "purpose": "Sent from the client to request resources/updated notifications from the server whenever a particular resource changes", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"resources/subscribe\"", + "params": "SubscribeRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "SubscribeRequestParams" + ], + "discriminator": { + "field": "method", + "value": "resources/subscribe" + } + }, + "UnsubscribeRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\UnsubscribeRequestParams", + "purpose": "Parameters for a `resources/unsubscribe` request", + "extends": "ResourceRequestParams", + "implements": [], + "properties": {}, + "usedBy": [ + "UnsubscribeRequest" + ], + "uses": [ + "ResourceRequestParams" + ] + }, + "UnsubscribeRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\UnsubscribeRequest", + "purpose": "Sent from the client to request cancellation of resources/updated notifications from the server", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"resources/unsubscribe\"", + "params": "UnsubscribeRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "UnsubscribeRequestParams" + ], + "discriminator": { + "field": "method", + "value": "resources/unsubscribe" + } + }, + "ResourceUpdatedNotificationParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceUpdatedNotificationParams", + "purpose": "Parameters for a `notifications/resources/updated` notification", + "extends": "NotificationParams", + "implements": [], + "properties": { + "uri": "string" + }, + "usedBy": [ + "ResourceUpdatedNotification" + ], + "uses": [ + "NotificationParams" + ] + }, + "ResourceUpdatedNotification": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceUpdatedNotification", + "purpose": "A notification from the server to the client, informing it that a resource has changed and may need to be read again", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/resources/updated\"", + "params": "ResourceUpdatedNotificationParams" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "ResourceUpdatedNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/resources/updated" + } + }, + "Resource": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\Resource", + "purpose": "A known resource that the server is capable of reading", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "uri": "string", + "description": "string?", + "mimeType": "string?", + "annotations": "Annotations?", + "size": "number?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ListResourcesResult", + "ResourceLink" + ], + "uses": [ + "BaseMetadata", + "Icons", + "Annotations" + ] + }, + "ResourceTemplate": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceTemplate", + "purpose": "A template description for resources available on the server", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "uriTemplate": "string", + "description": "string?", + "mimeType": "string?", + "annotations": "Annotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ListResourceTemplatesResult" + ], + "uses": [ + "BaseMetadata", + "Icons", + "Annotations" + ] + }, + "ResourceContents": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceContents", + "purpose": "The contents of a specific resource or sub-resource", + "implements": [], + "properties": { + "uri": "string", + "mimeType": "string?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "TextResourceContents", + "BlobResourceContents" + ], + "uses": [] + }, + "ListPromptsRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\ListPromptsRequest", + "purpose": "Sent from the client to request a list of prompts and prompt templates the server has", + "extends": "PaginatedRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"prompts/list\"" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "PaginatedRequest" + ], + "discriminator": { + "field": "method", + "value": "prompts/list" + } + }, + "ListPromptsResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\ListPromptsResult", + "purpose": "The server's response to a prompts/list request from the client", + "extends": "PaginatedResult", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "prompts": "Prompt[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "PaginatedResult", + "Prompt" + ] + }, + "GetPromptRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\GetPromptRequestParams", + "purpose": "Parameters for a `prompts/get` request", + "extends": "RequestParams", + "implements": [], + "properties": { + "name": "string", + "arguments": "{ [key: string]: string }?" + }, + "usedBy": [ + "GetPromptRequest" + ], + "uses": [ + "RequestParams" + ] + }, + "GetPromptRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\GetPromptRequest", + "purpose": "Used by the client to get a prompt provided by the server", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"prompts/get\"", + "params": "GetPromptRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "GetPromptRequestParams" + ], + "discriminator": { + "field": "method", + "value": "prompts/get" + } + }, + "GetPromptResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\GetPromptResult", + "purpose": "The server's response to a prompts/get request from the client", + "extends": "Result", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "description": "string?", + "messages": "PromptMessage[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "Result", + "PromptMessage" + ] + }, + "Prompt": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\Prompt", + "purpose": "A prompt or prompt template that the server offers", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "description": "string?", + "arguments": "PromptArgument[]?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ListPromptsResult" + ], + "uses": [ + "BaseMetadata", + "Icons", + "PromptArgument" + ] + }, + "PromptArgument": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\PromptArgument", + "purpose": "Describes an argument that a prompt can accept", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "description": "string?", + "required": "boolean?" + }, + "usedBy": [ + "Prompt" + ], + "uses": [ + "BaseMetadata" + ] + }, + "PromptMessage": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\PromptMessage", + "purpose": "Describes a message returned as part of a prompt", + "implements": [], + "properties": { + "role": "Role", + "content": "ContentBlock" + }, + "usedBy": [ + "GetPromptResult" + ], + "uses": [ + "Role", + "ContentBlock" + ] + }, + "ResourceLink": { + "kind": "class", + "domain": "Server", + "subdomain": "Resources", + "namespace": "WP\\McpSchema\\Server\\Resources\\ResourceLink", + "purpose": "A resource that the server is capable of reading, included in a prompt or tool call result", + "extends": "Resource", + "implements": [ + "ContentBlockInterface" + ], + "properties": { + "type": "\"resource_link\"" + }, + "usedBy": [ + "ContentBlockInterface" + ], + "uses": [ + "Resource" + ], + "discriminator": { + "field": "type", + "value": "resource_link" + } + }, + "PromptListChangedNotification": { + "kind": "class", + "domain": "Server", + "subdomain": "Prompts", + "namespace": "WP\\McpSchema\\Server\\Prompts\\PromptListChangedNotification", + "purpose": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/prompts/list_changed\"", + "params": "NotificationParams?" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "NotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/prompts/list_changed" + } + }, + "ListToolsRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ListToolsRequest", + "purpose": "Sent from the client to request a list of tools the server has", + "extends": "PaginatedRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"tools/list\"" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "PaginatedRequest" + ], + "discriminator": { + "field": "method", + "value": "tools/list" + } + }, + "ListToolsResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ListToolsResult", + "purpose": "The server's response to a tools/list request from the client", + "extends": "PaginatedResult", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "tools": "Tool[]" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "PaginatedResult", + "Tool" + ] + }, + "CallToolResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\CallToolResult", + "purpose": "The server's response to a tool call", + "extends": "Result", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "content": "ContentBlock[]", + "structuredContent": "{ [key: string]: unknown }?", + "isError": "boolean?" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "Result", + "ContentBlock" + ] + }, + "CallToolRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\CallToolRequestParams", + "purpose": "Parameters for a `tools/call` request", + "extends": "TaskAugmentedRequestParams", + "implements": [], + "properties": { + "name": "string", + "arguments": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "CallToolRequest" + ], + "uses": [ + "TaskAugmentedRequestParams" + ] + }, + "CallToolRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\CallToolRequest", + "purpose": "Used by the client to invoke a tool provided by the server", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"tools/call\"", + "params": "CallToolRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "CallToolRequestParams" + ], + "discriminator": { + "field": "method", + "value": "tools/call" + } + }, + "ToolListChangedNotification": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ToolListChangedNotification", + "purpose": "An optional notification from the server to the client, informing it that the list of tools it offers has changed", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/tools/list_changed\"", + "params": "NotificationParams?" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "NotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/tools/list_changed" + } + }, + "ToolAnnotations": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ToolAnnotations", + "purpose": "Additional properties describing a Tool to clients", + "implements": [], + "properties": { + "title": "string?", + "readOnlyHint": "boolean?", + "destructiveHint": "boolean?", + "idempotentHint": "boolean?", + "openWorldHint": "boolean?" + }, + "usedBy": [ + "Tool" + ], + "uses": [] + }, + "ToolExecution": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ToolExecution", + "purpose": "Execution-related properties for a tool", + "implements": [], + "properties": { + "taskSupport": "\"forbidden\" | \"optional\" | \"required\"?" + }, + "usedBy": [ + "Tool" + ], + "uses": [] + }, + "Tool": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\Tool", + "purpose": "Definition for a tool the client can call", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "description": "string?", + "inputSchema": "ToolInputSchema", + "execution": "ToolExecution?", + "outputSchema": "ToolOutputSchema | undefined?", + "annotations": "ToolAnnotations?", + "_meta": "{ [key: string]: unknown }?" + }, + "usedBy": [ + "ListToolsResult", + "CreateMessageRequestParams" + ], + "uses": [ + "BaseMetadata", + "Icons", + "ToolInputSchema", + "ToolExecution", + "ToolOutputSchema", + "ToolAnnotations" + ] + }, + "SetLevelRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Logging", + "namespace": "WP\\McpSchema\\Server\\Logging\\SetLevelRequestParams", + "purpose": "Parameters for a `logging/setLevel` request", + "extends": "RequestParams", + "implements": [], + "properties": { + "level": "LoggingLevel" + }, + "usedBy": [ + "SetLevelRequest" + ], + "uses": [ + "RequestParams", + "LoggingLevel" + ] + }, + "SetLevelRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Logging", + "namespace": "WP\\McpSchema\\Server\\Logging\\SetLevelRequest", + "purpose": "A request from the client to the server, to enable or adjust logging", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"logging/setLevel\"", + "params": "SetLevelRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "SetLevelRequestParams" + ], + "discriminator": { + "field": "method", + "value": "logging/setLevel" + } + }, + "LoggingMessageNotificationParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Logging", + "namespace": "WP\\McpSchema\\Server\\Logging\\LoggingMessageNotificationParams", + "purpose": "Parameters for a `notifications/message` notification", + "extends": "NotificationParams", + "implements": [], + "properties": { + "level": "LoggingLevel", + "logger": "string?", + "data": "unknown" + }, + "usedBy": [ + "LoggingMessageNotification" + ], + "uses": [ + "NotificationParams", + "LoggingLevel" + ] + }, + "LoggingMessageNotification": { + "kind": "class", + "domain": "Server", + "subdomain": "Logging", + "namespace": "WP\\McpSchema\\Server\\Logging\\LoggingMessageNotification", + "purpose": "JSONRPCNotification of a log message passed from server to client", + "extends": "JSONRPCNotification", + "implements": [ + "ServerNotificationInterface" + ], + "properties": { + "method": "\"notifications/message\"", + "params": "LoggingMessageNotificationParams" + }, + "usedBy": [ + "ServerNotificationInterface" + ], + "uses": [ + "JSONRPCNotification", + "LoggingMessageNotificationParams" + ], + "discriminator": { + "field": "method", + "value": "notifications/message" + } + }, + "CompleteRequestParams": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteRequestParams", + "purpose": "Parameters for a `completion/complete` request", + "extends": "RequestParams", + "implements": [], + "properties": { + "ref": "PromptReference | ResourceTemplateReference", + "argument": "CompleteRequestParamsArgument", + "context": "CompleteRequestParamsContext | undefined?" + }, + "usedBy": [ + "CompleteRequest" + ], + "uses": [ + "RequestParams", + "PromptReference", + "ResourceTemplateReference", + "CompleteRequestParamsArgument", + "CompleteRequestParamsContext" + ] + }, + "CompleteRequest": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteRequest", + "purpose": "A request from the client to the server, to ask for completion options", + "extends": "JSONRPCRequest", + "implements": [ + "ClientRequestInterface" + ], + "properties": { + "method": "\"completion/complete\"", + "params": "CompleteRequestParams" + }, + "usedBy": [ + "ClientRequestInterface" + ], + "uses": [ + "JSONRPCRequest", + "CompleteRequestParams" + ], + "discriminator": { + "field": "method", + "value": "completion/complete" + } + }, + "CompleteResult": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteResult", + "purpose": "The server's response to a completion/complete request", + "extends": "Result", + "implements": [ + "ServerResultInterface" + ], + "properties": { + "completion": "CompleteResultCompletion" + }, + "usedBy": [ + "ServerResultInterface" + ], + "uses": [ + "Result", + "CompleteResultCompletion" + ] + }, + "ResourceTemplateReference": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\ResourceTemplateReference", + "purpose": "A reference to a resource or resource template definition", + "implements": [], + "properties": { + "type": "\"ref/resource\"", + "uri": "string" + }, + "usedBy": [ + "CompleteRequestParams" + ], + "uses": [] + }, + "PromptReference": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\PromptReference", + "purpose": "Identifies a prompt", + "extends": "BaseMetadata", + "implements": [], + "properties": { + "type": "\"ref/prompt\"" + }, + "usedBy": [ + "CompleteRequestParams" + ], + "uses": [ + "BaseMetadata" + ] + }, + "ServerCapabilitiesPrompts": { + "kind": "class", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\ServerCapabilitiesPrompts", + "purpose": "Present if the server offers any prompt templates", + "implements": [], + "properties": { + "listChanged": "boolean?" + }, + "usedBy": [ + "ServerCapabilities" + ], + "uses": [] + }, + "ServerCapabilitiesResources": { + "kind": "class", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\ServerCapabilitiesResources", + "purpose": "Present if the server offers any resources to read", + "implements": [], + "properties": { + "subscribe": "boolean?", + "listChanged": "boolean?" + }, + "usedBy": [ + "ServerCapabilities" + ], + "uses": [] + }, + "ServerCapabilitiesTools": { + "kind": "class", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\ServerCapabilitiesTools", + "purpose": "Present if the server offers any tools to call", + "implements": [], + "properties": { + "listChanged": "boolean?" + }, + "usedBy": [ + "ServerCapabilities" + ], + "uses": [] + }, + "ServerCapabilitiesTasks": { + "kind": "class", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\ServerCapabilitiesTasks", + "purpose": "Present if the server supports task-augmented requests", + "implements": [], + "properties": { + "list": "object?", + "cancel": "object?" + }, + "usedBy": [ + "ServerCapabilities" + ], + "uses": [] + }, + "ToolInputSchema": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ToolInputSchema", + "purpose": "A JSON Schema object defining the expected parameters for the tool", + "implements": [], + "properties": { + "$schema": "string?", + "type": "\"object\"", + "properties": "{ [key: string]: object }?", + "required": "string[]?" + }, + "usedBy": [ + "Tool" + ], + "uses": [] + }, + "ToolOutputSchema": { + "kind": "class", + "domain": "Server", + "subdomain": "Tools", + "namespace": "WP\\McpSchema\\Server\\Tools\\ToolOutputSchema", + "purpose": "An optional JSON Schema object defining the structure of the tool's output returned in", + "implements": [], + "properties": { + "$schema": "string?", + "type": "\"object\"", + "properties": "{ [key: string]: object }?", + "required": "string[]?" + }, + "usedBy": [ + "Tool" + ], + "uses": [] + }, + "CompleteRequestParamsArgument": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteRequestParamsArgument", + "purpose": "The argument's information", + "implements": [], + "properties": { + "name": "string", + "value": "string" + }, + "usedBy": [ + "CompleteRequestParams" + ], + "uses": [] + }, + "CompleteRequestParamsContext": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteRequestParamsContext", + "purpose": "Additional, optional context for completions", + "implements": [], + "properties": { + "arguments": "{ [key: string]: string }?" + }, + "usedBy": [ + "CompleteRequestParams" + ], + "uses": [] + }, + "CompleteResultCompletion": { + "kind": "class", + "domain": "Server", + "subdomain": "Core", + "namespace": "WP\\McpSchema\\Server\\Core\\CompleteResultCompletion", + "purpose": "Complete Result Completion data structure", + "implements": [], + "properties": { + "values": "string[]", + "total": "number?", + "hasMore": "boolean?" + }, + "usedBy": [ + "CompleteResult" + ], + "uses": [] + }, + "LoggingLevelInterface": { + "kind": "union", + "domain": "Server", + "subdomain": "Logging", + "namespace": "WP\\McpSchema\\Server\\Logging\\Union\\LoggingLevelInterface", + "purpose": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [] + }, + "ServerNotificationInterface": { + "kind": "union", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\Union\\ServerNotificationInterface", + "purpose": "Union type: CancelledNotification | ProgressNotification | LoggingMessageNotification | ResourceUpdatedNotification | ResourceListChangedNotification | ToolListChangedNotification | PromptListChangedNotification | ElicitationCompleteNotification | TaskStatusNotification", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "CancelledNotification", + "ProgressNotification", + "LoggingMessageNotification", + "ResourceUpdatedNotification", + "ResourceListChangedNotification", + "ToolListChangedNotification", + "PromptListChangedNotification", + "ElicitationCompleteNotification", + "TaskStatusNotification" + ] + }, + "ServerResultInterface": { + "kind": "union", + "domain": "Server", + "subdomain": "Lifecycle", + "namespace": "WP\\McpSchema\\Server\\Lifecycle\\Union\\ServerResultInterface", + "purpose": "Union type: EmptyResult | InitializeResult | CompleteResult | GetPromptResult | ListPromptsResult | ListResourceTemplatesResult | ListResourcesResult | ReadResourceResult | CallToolResult | ListToolsResult | GetTaskResult | GetTaskPayloadResult | ListTasksResult | CancelTaskResult", + "implements": [], + "properties": {}, + "usedBy": [], + "uses": [ + "EmptyResult", + "InitializeResult", + "CompleteResult", + "GetPromptResult", + "ListPromptsResult", + "ListResourceTemplatesResult", + "ListResourcesResult", + "ReadResourceResult", + "CallToolResult", + "ListToolsResult", + "GetTaskResult", + "GetTaskPayloadResult", + "ListTasksResult", + "CancelTaskResult" + ] + } + }, + "factories": { + "ServerNotificationFactory": { + "interface": "ServerNotificationInterface", + "discriminator": "method", + "mappings": { + "notifications/cancelled": "CancelledNotification", + "notifications/progress": "ProgressNotification", + "notifications/message": "LoggingMessageNotification", + "notifications/resources/updated": "ResourceUpdatedNotification", + "notifications/resources/list_changed": "ResourceListChangedNotification", + "notifications/tools/list_changed": "ToolListChangedNotification", + "notifications/prompts/list_changed": "PromptListChangedNotification", + "notifications/elicitation/complete": "ElicitationCompleteNotification", + "notifications/tasks/status": "TaskStatusNotification" + } + } + } +} \ No newline at end of file diff --git a/skill/reference/client.md b/skill/reference/client.md new file mode 100644 index 0000000..3326ff1 --- /dev/null +++ b/skill/reference/client.md @@ -0,0 +1,118 @@ +# Client Domain Types + +## Contents + +- [Elicitation](#elicitation) (22 types) +- [Lifecycle](#lifecycle) (6 types) +- [Roots](#roots) (4 types) +- [Sampling](#sampling) (9 types) +- [Tasks](#tasks) (5 types) + +## Elicitation + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| BooleanSchema | Boolean Schema data structure | type: "boolean", title?: string, description?: string, +1 more | +| ElicitationCompleteNotification | An optional notification from the server to the client, i... | method: "notificatio..., params: ElicitationC... | +| ElicitationCompleteNotificationParams | Parameters for ElicitationCompleteNotification | elicitationId: string | +| ElicitRequest | A request from the server to elicit additional informatio... | method: "elicitation..., params: ElicitReques... | +| ElicitRequestFormParams | The parameters for a request to elicit non-sensitive info... | mode?: "form", message: string, requestedSchema: ElicitReques... | +| ElicitRequestFormParamsRequestedSchema | A restricted subset of JSON Schema | $schema?: string, type: "object", required?: string[] | +| ElicitRequestParamsInterface | The parameters for a request to elicit additional informa... | - | +| ElicitRequestURLParams | The parameters for a request to elicit information from t... | mode: "url", message: string, elicitationId: string, +1 more | +| ElicitResult | The client's response to an elicitation request | action: "accept" \| "..., content?: { [key: stri... | +| EnumSchemaInterface | Union type: SingleSelectEnumSchema \| MultiSelectEnumSchem... | - | +| LegacyTitledEnumSchema | Use TitledSingleSelectEnumSchema instead | type: "string", title?: string, description?: string, +3 more | +| MultiSelectEnumSchemaInterface | Union type: UntitledMultiSelectEnumSchema \| TitledMultiSe... | - | +| NumberSchema | Number Schema data structure | type: "number" \| "..., title?: string, description?: string, +3 more | +| PrimitiveSchemaDefinitionInterface | Restricted schema definitions that only allow primitive t... | - | +| SingleSelectEnumSchemaInterface | Union type: UntitledSingleSelectEnumSchema \| TitledSingle... | - | +| StringSchema | String Schema data structure | type: "string", title?: string, description?: string, +4 more | +| TitledMultiSelectEnumSchema | Schema for multiple-selection enumeration with display ti... | type: "array", title?: string, description?: string, +4 more | +| TitledMultiSelectEnumSchemaItems | Schema for array items with enum options and display labels | - | +| TitledSingleSelectEnumSchema | Schema for single-selection enumeration with display titl... | type: "string", title?: string, description?: string, +2 more | +| UntitledMultiSelectEnumSchema | Schema for multiple-selection enumeration without display... | type: "array", title?: string, description?: string, +4 more | +| UntitledMultiSelectEnumSchemaItems | Schema for the array items | type: "string", enum: string[] | +| UntitledSingleSelectEnumSchema | Schema for single-selection enumeration without display t... | type: "string", title?: string, description?: string, +2 more | + +### Relationships + +- `ElicitRequestFormParams` extends `TaskAugmentedRequestParams` +- `ElicitRequestFormParams` implements `ElicitRequestParamsInterface` +- `ElicitRequestURLParams` extends `TaskAugmentedRequestParams` +- `ElicitRequestURLParams` implements `ElicitRequestParamsInterface` +- `ElicitRequest` extends `JSONRPCRequest` + +## Lifecycle + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| ClientCapabilities | Capabilities a client may support | experimental?: { [key: stri..., roots?: ClientCapabi..., sampling?: ClientCapabi..., +2 more | +| ClientCapabilitiesElicitation | Present if the client supports elicitation from the server | form?: object, url?: object | +| ClientCapabilitiesRoots | Present if the client supports listing roots | listChanged?: boolean | +| ClientCapabilitiesSampling | Present if the client supports sampling from an LLM | context?: object, tools?: object | +| ClientCapabilitiesTasks | Present if the client supports task-augmented requests | list?: object, cancel?: object | +| ClientResultInterface | Union type: EmptyResult \| CreateMessageResult \| ListRoots... | - | + +## Roots + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| ListRootsRequest | Sent from the server to request a list of root URIs from ... | method: "roots/list", params?: RequestParams | +| ListRootsResult | The client's response to a roots/list request from the se... | roots: Root[] | +| Root | Represents a root directory or file that the server can o... | uri: string, name?: string, _meta?: { [key: stri... | +| RootsListChangedNotification | A notification from the client to the server, informing i... | method: "notificatio..., params?: Notification... | + +### Relationships + +- `ListRootsRequest` extends `JSONRPCRequest` +- `ListRootsRequest` implements `ServerRequestInterface` +- `ListRootsResult` extends `Result` +- `ListRootsResult` implements `ClientResultInterface` +- `RootsListChangedNotification` extends `JSONRPCNotification` + +## Sampling + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| CreateMessageRequest | A request from the server to sample an LLM via the client | method: "sampling/cr..., params: CreateMessag... | +| CreateMessageRequestParams | Parameters for a `sampling/createMessage` request | messages: SamplingMess..., modelPreferences?: ModelPrefere..., systemPrompt?: string, +7 more | +| CreateMessageResult | The client's response to a sampling/createMessage request... | model: string, stopReason?: "endTurn" \| ... | +| ModelHint | Hints to use for model selection | name?: string | +| ModelPreferences | The server's preferences for model selection, requested o... | hints?: ModelHint[], costPriority?: number, speedPriority?: number, +1 more | +| SamplingMessage | Describes a message issued to or received from an LLM API | role: Role, content: SamplingMess..., _meta?: { [key: stri... | +| ToolChoice | Controls tool selection behavior for sampling requests | mode?: "auto" \| "re... | +| ToolResultContent | The result of a tool use, provided by the user back to th... | type: "tool_result", toolUseId: string, content: ContentBlock[], +3 more | +| ToolUseContent | A request from the assistant to call a tool | type: "tool_use", id: string, name: string, +2 more | + +### Relationships + +- `CreateMessageRequestParams` extends `TaskAugmentedRequestParams` +- `CreateMessageRequest` extends `JSONRPCRequest` +- `CreateMessageRequest` implements `ServerRequestInterface` +- `CreateMessageResult` extends `Result` +- `CreateMessageResult` implements `ClientResultInterface` + +## Tasks + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| CreateTaskResult | A response to a task-augmented request | task: Task | +| RelatedTaskMetadata | Metadata for associating messages with a task | taskId: string | +| Task | Data associated with a task | taskId: string, status: TaskStatus, statusMessage?: string, +4 more | +| TaskMetadata | Metadata for augmenting a request with task execution | ttl?: number | +| TaskStatusInterface | The status of a task | - | + +### Relationships + +- `CreateTaskResult` extends `Result` diff --git a/skill/reference/common.md b/skill/reference/common.md new file mode 100644 index 0000000..4b38cd0 --- /dev/null +++ b/skill/reference/common.md @@ -0,0 +1,148 @@ +# Common Domain Types + +## Contents + +- [Content](#content) (3 types) +- [Core](#core) (1 types) +- [JsonRpc](#jsonrpc) (13 types) +- [Lifecycle](#lifecycle) (1 types) +- [Protocol](#protocol) (31 types) +- [Tasks](#tasks) (10 types) + +## Content + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| AudioContent | Audio provided to or from an LLM | type: "audio", data: string, mimeType: string, +2 more | +| ImageContent | An image provided to or from an LLM | type: "image", data: string, mimeType: string, +2 more | +| TextContent | Text provided to or from an LLM | type: "text", text: string, annotations?: Annotations, +1 more | + +### Relationships + +- `TextContent` implements `SamplingMessageContentBlockInterface` +- `TextContent` implements `ContentBlockInterface` +- `ImageContent` implements `SamplingMessageContentBlockInterface` +- `ImageContent` implements `ContentBlockInterface` +- `AudioContent` implements `SamplingMessageContentBlockInterface` + +## Core + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| Icon | An optionally-sized icon that can be displayed in a user ... | src: string, mimeType?: string, sizes?: string[], +1 more | + +## JsonRpc + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| Error | Error data structure | code: number, message: string, data?: unknown | +| JSONRPCErrorResponse | A response to a request that indicates an error occurred | jsonrpc: typeof JSONR..., id?: RequestId, error: Error | +| JSONRPCMessageInterface | Refers to any valid JSON-RPC object that can be decoded o... | - | +| JSONRPCNotification | A notification which does not expect a response | jsonrpc: typeof JSONR... | +| JSONRPCRequest | A request that expects a response | jsonrpc: typeof JSONR..., id: RequestId | +| JSONRPCResponseInterface | A response to a request, containing either the result or ... | - | +| JSONRPCResultResponse | A successful (non-error) response to a request | jsonrpc: typeof JSONR..., id: RequestId, result: Result | +| Notification | Notification for events | method: string, params?: { [key: stri... | +| NotificationParams | Parameters for Notification | _meta?: { [key: stri... | +| Request | Request for operation | method: string, params?: { [key: stri... | +| RequestIdInterface | A uniquely identifying ID for a request in JSON-RPC | - | +| RequestParams | Common params for any request | _meta?: RequestParam... | +| RequestParamsMeta | See [General fields: `_meta`](/specification/2025-11-25/b... | progressToken?: ProgressToken | + +### Relationships + +- `JSONRPCRequest` extends `Request` +- `JSONRPCRequest` implements `JSONRPCMessageInterface` +- `JSONRPCNotification` extends `Notification` +- `JSONRPCNotification` implements `JSONRPCMessageInterface` +- `JSONRPCResultResponse` implements `JSONRPCResponseInterface` + +## Lifecycle + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| Implementation | Describes the MCP implementation | version: string, description?: string, websiteUrl?: string | + +### Relationships + +- `Implementation` extends `BaseMetadata` + +## Protocol + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| Annotations | Optional annotations for the client | audience?: Role[], priority?: number, lastModified?: string | +| BaseMetadata | Base interface for metadata with name (identifier) and ti... | name: string, title?: string | +| BlobResourceContents | Blob Resource Contents data structure | blob: string | +| CancelledNotification | This notification can be sent by either side to indicate ... | method: "notificatio..., params: CancelledNot... | +| CancelledNotificationParams | Parameters for a `notifications/cancelled` notification | requestId?: RequestId, reason?: string | +| ClientNotificationInterface | Union type: CancelledNotification \| ProgressNotification ... | - | +| ClientRequestInterface | Union type: PingRequest \| InitializeRequest \| CompleteReq... | - | +| ContentBlockInterface | Union type: TextContent \| ImageContent \| AudioContent \| R... | - | +| EmbeddedResource | The contents of a resource, embedded into a prompt or too... | type: "resource", resource: TextResource..., annotations?: Annotations, +1 more | +| EmptyResult | A response that indicates success but carries no data | - | +| GetTaskPayloadRequest | A request to retrieve the result of a completed task | method: "tasks/result", params: GetTaskPaylo... | +| GetTaskPayloadRequestParams | Parameters for GetTaskPayloadRequest | taskId: string | +| GetTaskPayloadResult | The response to a tasks/result request | - | +| Icons | Base interface to add `icons` property | icons?: Icon[] | +| InitializedNotification | This notification is sent from the client to the server a... | method: "notificatio..., params?: Notification... | +| InitializeRequest | This request is sent from the client to the server when i... | method: "initialize", params: InitializeRe... | +| InitializeRequestParams | Parameters for an `initialize` request | protocolVersion: string, capabilities: ClientCapabi..., clientInfo: Implementation | +| InitializeResult | After receiving an initialize request from the client, th... | protocolVersion: string, capabilities: ServerCapabi..., serverInfo: Implementation, +1 more | +| PaginatedRequest | Request for Paginated operation | params?: PaginatedReq... | +| PaginatedRequestParams | Common parameters for paginated requests | cursor?: Cursor | +| PaginatedResult | Result from Paginated operation | nextCursor?: Cursor | +| PingRequest | A ping, issued by either the server or the client, to che... | method: "ping", params?: RequestParams | +| ProgressNotification | An out-of-band notification used to inform the receiver o... | method: "notificatio..., params: ProgressNoti... | +| ProgressNotificationParams | Parameters for a `notifications/progress` notification | progressToken: ProgressToken, progress: number, total?: number, +1 more | +| ProgressTokenInterface | A progress token, used to associate progress notification... | - | +| Result | Result from operation | _meta?: { [key: stri... | +| Role | The sender or recipient of messages and data in a convers... | USER: user, ASSISTANT: assistant | +| SamplingMessageContentBlockInterface | Union type: TextContent \| ImageContent \| AudioContent \| T... | - | +| ServerRequestInterface | Union type: PingRequest \| CreateMessageRequest \| ListRoot... | - | +| TextResourceContents | Text Resource Contents data structure | text: string | +| URLElicitationRequiredError | An error response that indicates that the server requires... | error: Error & { + ... | + +### Relationships + +- `URLElicitationRequiredError` extends `Omit` +- `CancelledNotificationParams` extends `NotificationParams` +- `CancelledNotification` extends `JSONRPCNotification` +- `CancelledNotification` implements `ClientNotificationInterface` +- `CancelledNotification` implements `ServerNotificationInterface` + +## Tasks + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| CancelTaskRequest | A request to cancel a task | method: "tasks/cancel", params: CancelTaskRe... | +| CancelTaskRequestParams | Parameters for CancelTaskRequest | taskId: string | +| CancelTaskResult | The response to a tasks/cancel request | taskId: string, status: "working" \| ..., statusMessage?: string, +4 more | +| GetTaskRequest | A request to retrieve the state of a task | method: "tasks/get", params: GetTaskReque... | +| GetTaskRequestParams | Parameters for GetTaskRequest | taskId: string | +| GetTaskResult | The response to a tasks/get request | taskId: string, status: "working" \| ..., statusMessage?: string, +4 more | +| ListTasksRequest | A request to retrieve a list of tasks | method: "tasks/list" | +| ListTasksResult | The response to a tasks/list request | tasks: Task[] | +| TaskAugmentedRequestParams | Common params for any task-augmented request | task?: TaskMetadata | +| TaskStatusNotification | An optional notification from the receiver to the request... | method: "notificatio..., params: TaskStatusNo... | + +### Relationships + +- `TaskAugmentedRequestParams` extends `RequestParams` +- `GetTaskRequest` extends `JSONRPCRequest` +- `GetTaskRequest` implements `ClientRequestInterface` +- `GetTaskRequest` implements `ServerRequestInterface` +- `CancelTaskRequest` extends `JSONRPCRequest` diff --git a/skill/reference/factories.md b/skill/reference/factories.md new file mode 100644 index 0000000..5ebca5f --- /dev/null +++ b/skill/reference/factories.md @@ -0,0 +1,178 @@ +# Factory Classes Reference + +Factories instantiate the correct DTO based on discriminator values. + +## Common Factories + +### SamplingMessageContentBlockFactory + +- **Interface:** `SamplingMessageContentBlockInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `text` | TextContent | +| `image` | ImageContent | +| `audio` | AudioContent | +| `tool_use` | ToolUseContent | +| `tool_result` | ToolResultContent | + +### ContentBlockFactory + +- **Interface:** `ContentBlockInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `text` | TextContent | +| `image` | ImageContent | +| `audio` | AudioContent | +| `resource_link` | ResourceLink | +| `resource` | EmbeddedResource | + +### ClientRequestFactory + +- **Interface:** `ClientRequestInterface` +- **Discriminator:** `method` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `ping` | PingRequest | +| `initialize` | InitializeRequest | +| `completion/complete` | CompleteRequest | +| `logging/setLevel` | SetLevelRequest | +| `prompts/get` | GetPromptRequest | +| `prompts/list` | ListPromptsRequest | +| `resources/list` | ListResourcesRequest | +| `resources/templates/list` | ListResourceTemplatesRequest | +| `resources/read` | ReadResourceRequest | +| `resources/subscribe` | SubscribeRequest | +| `resources/unsubscribe` | UnsubscribeRequest | +| `tools/call` | CallToolRequest | +| `tools/list` | ListToolsRequest | +| `tasks/get` | GetTaskRequest | +| `tasks/result` | GetTaskPayloadRequest | +| `tasks/list` | ListTasksRequest | +| `tasks/cancel` | CancelTaskRequest | + +### ClientNotificationFactory + +- **Interface:** `ClientNotificationInterface` +- **Discriminator:** `method` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `notifications/cancelled` | CancelledNotification | +| `notifications/progress` | ProgressNotification | +| `notifications/initialized` | InitializedNotification | +| `notifications/roots/list_changed` | RootsListChangedNotification | +| `notifications/tasks/status` | TaskStatusNotification | + +### ServerRequestFactory + +- **Interface:** `ServerRequestInterface` +- **Discriminator:** `method` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `ping` | PingRequest | +| `sampling/createMessage` | CreateMessageRequest | +| `roots/list` | ListRootsRequest | +| `elicitation/create` | ElicitRequest | +| `tasks/get` | GetTaskRequest | +| `tasks/result` | GetTaskPayloadRequest | +| `tasks/list` | ListTasksRequest | +| `tasks/cancel` | CancelTaskRequest | + + +## Client Factories + +### ElicitRequestParamsFactory + +- **Interface:** `ElicitRequestParamsInterface` +- **Discriminator:** `mode` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `form` | ElicitRequestFormParams | +| `url` | ElicitRequestURLParams | + +### PrimitiveSchemaDefinitionFactory + +- **Interface:** `PrimitiveSchemaDefinitionInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `string` | StringSchema | +| `number" \| "integer` | NumberSchema | +| `boolean` | BooleanSchema | + +### SingleSelectEnumSchemaFactory + +- **Interface:** `SingleSelectEnumSchemaInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `string` | TitledSingleSelectEnumSchema | + +### MultiSelectEnumSchemaFactory + +- **Interface:** `MultiSelectEnumSchemaInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `array` | TitledMultiSelectEnumSchema | + +### EnumSchemaFactory + +- **Interface:** `EnumSchemaInterface` +- **Discriminator:** `type` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `string` | LegacyTitledEnumSchema | + + +## Server Factories + +### ServerNotificationFactory + +- **Interface:** `ServerNotificationInterface` +- **Discriminator:** `method` + +**Mappings:** + +| Value | Type | +| --- | --- | +| `notifications/cancelled` | CancelledNotification | +| `notifications/progress` | ProgressNotification | +| `notifications/message` | LoggingMessageNotification | +| `notifications/resources/updated` | ResourceUpdatedNotification | +| `notifications/resources/list_changed` | ResourceListChangedNotification | +| `notifications/tools/list_changed` | ToolListChangedNotification | +| `notifications/prompts/list_changed` | PromptListChangedNotification | +| `notifications/elicitation/complete` | ElicitationCompleteNotification | +| `notifications/tasks/status` | TaskStatusNotification | diff --git a/skill/reference/overview.md b/skill/reference/overview.md new file mode 100644 index 0000000..b2f0f7e --- /dev/null +++ b/skill/reference/overview.md @@ -0,0 +1,40 @@ +# MCP PHP Schema Overview + +Version: 2025-11-25 +Namespace: `WP\McpSchema` + +## Architecture + +The schema follows the Model Context Protocol specification. +Types are organized into three domains: + +- **Common**: Base types, JSON-RPC, content blocks +- **Server**: Resources, tools, prompts, logging +- **Client**: Sampling, elicitation, roots, tasks + +## Type Hierarchy + +``` +Request (base for all requests) +├── PaginatedRequest +├── [Domain]Request types + +Result (base for all results) +├── PaginatedResult +├── [Domain]Result types + +Notification (base for notifications) +├── [Domain]Notification types +``` + +## Union Interfaces + +Union types are represented as interfaces with factory classes: + +| Union | Purpose | +| --- | --- | +| `ClientRequestInterface` | All requests from client to server | +| `ServerRequestInterface` | All requests from server to client | +| `ClientResultInterface` | All results for client requests | +| `ServerResultInterface` | All results for server requests | +| `ContentBlockInterface` | Text, image, audio, resource content | diff --git a/skill/reference/rpc-methods.md b/skill/reference/rpc-methods.md new file mode 100644 index 0000000..1980dc7 --- /dev/null +++ b/skill/reference/rpc-methods.md @@ -0,0 +1,36 @@ +# RPC Methods Reference + +## Client → Server + +| Method | Direction | Request | Result | +| --- | --- | --- | --- | +| `initialize` | client→server | InitializeRequest | InitializeResult | +| `resources/list` | client→server | ListResourcesRequest | ListResourcesResult | +| `resources/templates/list` | client→server | ListResourceTemplatesRequest | ListResourceTemplatesResult | +| `resources/read` | client→server | ReadResourceRequest | ReadResourceResult | +| `resources/subscribe` | client→server | SubscribeRequest | Result | +| `resources/unsubscribe` | client→server | UnsubscribeRequest | Result | +| `prompts/list` | client→server | ListPromptsRequest | ListPromptsResult | +| `prompts/get` | client→server | GetPromptRequest | GetPromptResult | +| `tools/list` | client→server | ListToolsRequest | ListToolsResult | +| `tools/call` | client→server | CallToolRequest | CallToolResult | +| `logging/setLevel` | client→server | SetLevelRequest | Result | +| `completion/complete` | client→server | CompleteRequest | CompleteResult | + +## Server → Client + +| Method | Direction | Request | Result | +| --- | --- | --- | --- | +| `sampling/createMessage` | server→client | CreateMessageRequest | CreateMessageResult | +| `roots/list` | server→client | ListRootsRequest | ListRootsResult | +| `elicitation/create` | server→client | ElicitRequest | ElicitResult | + +## Bidirectional + +| Method | Direction | Request | Result | +| --- | --- | --- | --- | +| `ping` | bidirectional | PingRequest | Result | +| `tasks/get` | bidirectional | GetTaskRequest | Result | +| `tasks/result` | bidirectional | GetTaskPayloadRequest | GetTaskPayloadResult | +| `tasks/cancel` | bidirectional | CancelTaskRequest | Result | +| `tasks/list` | bidirectional | ListTasksRequest | ListTasksResult | diff --git a/skill/reference/server.md b/skill/reference/server.md new file mode 100644 index 0000000..180ccdb --- /dev/null +++ b/skill/reference/server.md @@ -0,0 +1,151 @@ +# Server Domain Types + +## Contents + +- [Core](#core) (8 types) +- [Lifecycle](#lifecycle) (7 types) +- [Logging](#logging) (5 types) +- [Prompts](#prompts) (9 types) +- [Resources](#resources) (19 types) +- [Tools](#tools) (11 types) + +## Core + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| CompleteRequest | A request from the client to the server, to ask for compl... | method: "completion/..., params: CompleteRequ... | +| CompleteRequestParams | Parameters for a `completion/complete` request | ref: PromptRefere..., argument: CompleteRequ..., context?: CompleteRequ... | +| CompleteRequestParamsArgument | The argument's information | name: string, value: string | +| CompleteRequestParamsContext | Additional, optional context for completions | arguments?: { [key: stri... | +| CompleteResult | The server's response to a completion/complete request | completion: CompleteResu... | +| CompleteResultCompletion | Complete Result Completion data structure | values: string[], total?: number, hasMore?: boolean | +| PromptReference | Identifies a prompt | type: "ref/prompt" | +| ResourceTemplateReference | A reference to a resource or resource template definition | type: "ref/resource", uri: string | + +### Relationships + +- `CompleteRequestParams` extends `RequestParams` +- `CompleteRequest` extends `JSONRPCRequest` +- `CompleteRequest` implements `ClientRequestInterface` +- `CompleteResult` extends `Result` +- `CompleteResult` implements `ServerResultInterface` + +## Lifecycle + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| ServerCapabilities | Capabilities that a server may support | experimental?: { [key: stri..., logging?: object, completions?: object, +4 more | +| ServerCapabilitiesPrompts | Present if the server offers any prompt templates | listChanged?: boolean | +| ServerCapabilitiesResources | Present if the server offers any resources to read | subscribe?: boolean, listChanged?: boolean | +| ServerCapabilitiesTasks | Present if the server supports task-augmented requests | list?: object, cancel?: object | +| ServerCapabilitiesTools | Present if the server offers any tools to call | listChanged?: boolean | +| ServerNotificationInterface | Union type: CancelledNotification \| ProgressNotification ... | - | +| ServerResultInterface | Union type: EmptyResult \| InitializeResult \| CompleteResu... | - | + +## Logging + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| LoggingLevelInterface | The severity of a log message | - | +| LoggingMessageNotification | JSONRPCNotification of a log message passed from server t... | method: "notificatio..., params: LoggingMessa... | +| LoggingMessageNotificationParams | Parameters for a `notifications/message` notification | level: LoggingLevel, logger?: string, data: unknown | +| SetLevelRequest | A request from the client to the server, to enable or adj... | method: "logging/set..., params: SetLevelRequ... | +| SetLevelRequestParams | Parameters for a `logging/setLevel` request | level: LoggingLevel | + +### Relationships + +- `SetLevelRequestParams` extends `RequestParams` +- `SetLevelRequest` extends `JSONRPCRequest` +- `SetLevelRequest` implements `ClientRequestInterface` +- `LoggingMessageNotificationParams` extends `NotificationParams` +- `LoggingMessageNotification` extends `JSONRPCNotification` + +## Prompts + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| GetPromptRequest | Used by the client to get a prompt provided by the server | method: "prompts/get", params: GetPromptReq... | +| GetPromptRequestParams | Parameters for a `prompts/get` request | name: string, arguments?: { [key: stri... | +| GetPromptResult | The server's response to a prompts/get request from the c... | description?: string, messages: PromptMessage[] | +| ListPromptsRequest | Sent from the client to request a list of prompts and pro... | method: "prompts/list" | +| ListPromptsResult | The server's response to a prompts/list request from the ... | prompts: Prompt[] | +| Prompt | A prompt or prompt template that the server offers | description?: string, arguments?: PromptArgume..., _meta?: { [key: stri... | +| PromptArgument | Describes an argument that a prompt can accept | description?: string, required?: boolean | +| PromptListChangedNotification | An optional notification from the server to the client, i... | method: "notificatio..., params?: Notification... | +| PromptMessage | Describes a message returned as part of a prompt | role: Role, content: ContentBlock | + +### Relationships + +- `ListPromptsRequest` extends `PaginatedRequest` +- `ListPromptsRequest` implements `ClientRequestInterface` +- `ListPromptsResult` extends `PaginatedResult` +- `ListPromptsResult` implements `ServerResultInterface` +- `GetPromptRequestParams` extends `RequestParams` + +## Resources + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| ListResourcesRequest | Sent from the client to request a list of resources the s... | method: "resources/l... | +| ListResourcesResult | The server's response to a resources/list request from th... | resources: Resource[] | +| ListResourceTemplatesRequest | Sent from the client to request a list of resource templa... | method: "resources/t... | +| ListResourceTemplatesResult | The server's response to a resources/templates/list reque... | resourceTemplates: ResourceTemp... | +| ReadResourceRequest | Sent from the client to the server, to read a specific re... | method: "resources/r..., params: ReadResource... | +| ReadResourceRequestParams | Parameters for a `resources/read` request | - | +| ReadResourceResult | The server's response to a resources/read request from th... | contents: (TextResourc... | +| Resource | A known resource that the server is capable of reading | uri: string, description?: string, mimeType?: string, +3 more | +| ResourceContents | The contents of a specific resource or sub-resource | uri: string, mimeType?: string, _meta?: { [key: stri... | +| ResourceLink | A resource that the server is capable of reading, include... | type: "resource_link" | +| ResourceListChangedNotification | An optional notification from the server to the client, i... | method: "notificatio..., params?: Notification... | +| ResourceRequestParams | Common parameters when working with resources | uri: string | +| ResourceTemplate | A template description for resources available on the server | uriTemplate: string, description?: string, mimeType?: string, +2 more | +| ResourceUpdatedNotification | A notification from the server to the client, informing i... | method: "notificatio..., params: ResourceUpda... | +| ResourceUpdatedNotificationParams | Parameters for a `notifications/resources/updated` notifi... | uri: string | +| SubscribeRequest | Sent from the client to request resources/updated notific... | method: "resources/s..., params: SubscribeReq... | +| SubscribeRequestParams | Parameters for a `resources/subscribe` request | - | +| UnsubscribeRequest | Sent from the client to request cancellation of resources... | method: "resources/u..., params: UnsubscribeR... | +| UnsubscribeRequestParams | Parameters for a `resources/unsubscribe` request | - | + +### Relationships + +- `ListResourcesRequest` extends `PaginatedRequest` +- `ListResourcesRequest` implements `ClientRequestInterface` +- `ListResourcesResult` extends `PaginatedResult` +- `ListResourcesResult` implements `ServerResultInterface` +- `ListResourceTemplatesRequest` extends `PaginatedRequest` + +## Tools + +### Types + +| Type | Purpose | Key Properties | +| --- | --- | --- | +| CallToolRequest | Used by the client to invoke a tool provided by the server | method: "tools/call", params: CallToolRequ... | +| CallToolRequestParams | Parameters for a `tools/call` request | name: string, arguments?: { [key: stri... | +| CallToolResult | The server's response to a tool call | content: ContentBlock[], structuredContent?: { [key: stri..., isError?: boolean | +| ListToolsRequest | Sent from the client to request a list of tools the serve... | method: "tools/list" | +| ListToolsResult | The server's response to a tools/list request from the cl... | tools: Tool[] | +| Tool | Definition for a tool the client can call | description?: string, inputSchema: ToolInputSchema, execution?: ToolExecution, +3 more | +| ToolAnnotations | Additional properties describing a Tool to clients | title?: string, readOnlyHint?: boolean, destructiveHint?: boolean, +2 more | +| ToolExecution | Execution-related properties for a tool | taskSupport?: "forbidden" ... | +| ToolInputSchema | A JSON Schema object defining the expected parameters for... | $schema?: string, type: "object", properties?: { [key: stri..., +1 more | +| ToolListChangedNotification | An optional notification from the server to the client, i... | method: "notificatio..., params?: Notification... | +| ToolOutputSchema | An optional JSON Schema object defining the structure of ... | $schema?: string, type: "object", properties?: { [key: stri..., +1 more | + +### Relationships + +- `ListToolsRequest` extends `PaginatedRequest` +- `ListToolsRequest` implements `ClientRequestInterface` +- `ListToolsResult` extends `PaginatedResult` +- `ListToolsResult` implements `ServerResultInterface` +- `CallToolResult` extends `Result` diff --git a/skill/scripts/find-rpc.sh b/skill/scripts/find-rpc.sh new file mode 100755 index 0000000..fd419df --- /dev/null +++ b/skill/scripts/find-rpc.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Find RPC method details +# Usage: ./find-rpc.sh +# Output: Request/Result types for matching methods + +SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PATTERN="$1" + +if [ -z "$PATTERN" ]; then + echo "Usage: ./find-rpc.sh " + exit 1 +fi + +jq -r ".rpcMethods[] | select(.method | test(\"$PATTERN\"; \"i\")) | \"\(.method): \(.direction)\"" "$SKILL_DIR/data/schema-index.json" 2>/dev/null + +# Also search for full details in domain files +for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + jq -r ".types | to_entries[] | select(.key | endswith(\"Request\")) | select(.value.discriminator.value | test(\"$PATTERN\"; \"i\") // false) | \"\(.value.discriminator.value): \(.key) → \(.key | sub(\"Request$\"; \"Result\"))\"" "$f" 2>/dev/null + fi +done diff --git a/skill/scripts/get-type.sh b/skill/scripts/get-type.sh new file mode 100755 index 0000000..eb6cf79 --- /dev/null +++ b/skill/scripts/get-type.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Get full details for a specific type +# Usage: ./get-type.sh +# Output: JSON with type details, relationships, usage + +SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TYPE_NAME="$1" + +if [ -z "$TYPE_NAME" ]; then + echo "Usage: ./get-type.sh " + exit 1 +fi + +for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + result=$(jq -r ".types.\"$TYPE_NAME\" // empty" "$f" 2>/dev/null) + if [ -n "$result" ]; then + echo "$result" | jq . + exit 0 + fi + fi +done + +echo "Type '$TYPE_NAME' not found" +exit 1 diff --git a/skill/scripts/search-types.sh b/skill/scripts/search-types.sh new file mode 100755 index 0000000..c215667 --- /dev/null +++ b/skill/scripts/search-types.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Search for types by name or property +# Usage: ./search-types.sh [domain] +# Output: Matching type names and locations + +SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PATTERN="$1" +DOMAIN="${2:-all}" + +if [ -z "$PATTERN" ]; then + echo "Usage: ./search-types.sh [domain]" + echo " domain: all, common, server, client" + exit 1 +fi + +search_file() { + local file="$1" + if [ -f "$file" ]; then + jq -r ".types | to_entries[] | select(.key | test(\"$PATTERN\"; \"i\")) | \"\(.key) (\(.value.domain)/\(.value.subdomain))\"" "$file" 2>/dev/null + fi +} + +if [ "$DOMAIN" = "all" ]; then + for f in "$SKILL_DIR"/data/schema-*.json; do + if [ "$(basename "$f")" != "schema-index.json" ]; then + search_file "$f" + fi + done +else + search_file "$SKILL_DIR/data/schema-$DOMAIN.json" +fi diff --git a/src/Client/Elicitation/BooleanSchema.php b/src/Client/Elicitation/BooleanSchema.php new file mode 100644 index 0000000..f7ed258 --- /dev/null +++ b/src/Client/Elicitation/BooleanSchema.php @@ -0,0 +1,146 @@ +type = self::TYPE; + $this->title = $title; + $this->description = $description; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'boolean', + * title?: string|null, + * description?: string|null, + * default?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asBoolOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'boolean' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return bool|null + */ + public function getDefault(): ?bool + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/ElicitRequest.php b/src/Client/Elicitation/ElicitRequest.php new file mode 100644 index 0000000..a3905d5 --- /dev/null +++ b/src/Client/Elicitation/ElicitRequest.php @@ -0,0 +1,106 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'elicitation/create', + * params: array|\WP\McpSchema\Client\Elicitation\Union\ElicitRequestParamsInterface + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Client\Elicitation\Union\ElicitRequestParamsInterface $params */ + $params = is_array($data['params']) + ? ElicitRequestParamsFactory::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Client\Elicitation\Union\ElicitRequestParamsInterface + */ + public function getTypedParams(): ElicitRequestParamsInterface + { + return $this->typedParams; + } +} diff --git a/src/Client/Elicitation/ElicitRequestFormParams.php b/src/Client/Elicitation/ElicitRequestFormParams.php new file mode 100644 index 0000000..3de8bc0 --- /dev/null +++ b/src/Client/Elicitation/ElicitRequestFormParams.php @@ -0,0 +1,162 @@ +mode = self::MODE; + $this->message = $message; + $this->requestedSchema = $requestedSchema; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * task?: array|\WP\McpSchema\Client\Tasks\TaskMetadata|null, + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * mode?: 'form'|null, + * message: string, + * requestedSchema: array|\WP\McpSchema\Client\Elicitation\ElicitRequestFormParamsRequestedSchema + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['message', 'requestedSchema']); + + /** @var \WP\McpSchema\Client\Elicitation\ElicitRequestFormParamsRequestedSchema $requestedSchema */ + $requestedSchema = is_array($data['requestedSchema']) + ? ElicitRequestFormParamsRequestedSchema::fromArray(self::asArray($data['requestedSchema'])) + : $data['requestedSchema']; + + /** @var \WP\McpSchema\Client\Tasks\TaskMetadata|null $task */ + $task = isset($data['task']) + ? (is_array($data['task']) + ? TaskMetadata::fromArray(self::asArray($data['task'])) + : $data['task']) + : null; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['message']), + $requestedSchema, + $task, + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->mode !== null) { + $result['mode'] = $this->mode; + } + $result['message'] = $this->message; + $result['requestedSchema'] = $this->requestedSchema->toArray(); + + return $result; + } + + /** + * @return 'form'|null + */ + public function getMode(): ?string + { + return $this->mode; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @return \WP\McpSchema\Client\Elicitation\ElicitRequestFormParamsRequestedSchema + */ + public function getRequestedSchema(): ElicitRequestFormParamsRequestedSchema + { + return $this->requestedSchema; + } +} diff --git a/src/Client/Elicitation/ElicitRequestFormParamsRequestedSchema.php b/src/Client/Elicitation/ElicitRequestFormParamsRequestedSchema.php new file mode 100644 index 0000000..87f2596 --- /dev/null +++ b/src/Client/Elicitation/ElicitRequestFormParamsRequestedSchema.php @@ -0,0 +1,114 @@ +|null + */ + protected ?array $required; + + /** + * @param string|null $schema + * @param array|null $required + */ + public function __construct( + ?string $schema = null, + ?array $required = null + ) { + $this->type = self::TYPE; + $this->schema = $schema; + $this->required = $required; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * '$schema'?: string|null, + * type: 'object', + * required?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['$schema'] ?? null), + self::asStringArrayOrNull($data['required'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->schema !== null) { + $result['$schema'] = $this->schema; + } + $result['type'] = $this->type; + if ($this->required !== null) { + $result['required'] = $this->required; + } + + return $result; + } + + /** + * @return string|null + */ + public function getSchema(): ?string + { + return $this->schema; + } + + /** + * @return 'object' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return array|null + */ + public function getRequired(): ?array + { + return $this->required; + } +} diff --git a/src/Client/Elicitation/ElicitRequestURLParams.php b/src/Client/Elicitation/ElicitRequestURLParams.php new file mode 100644 index 0000000..9ee1a73 --- /dev/null +++ b/src/Client/Elicitation/ElicitRequestURLParams.php @@ -0,0 +1,178 @@ +mode = self::MODE; + $this->message = $message; + $this->elicitationId = $elicitationId; + $this->url = $url; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * task?: array|\WP\McpSchema\Client\Tasks\TaskMetadata|null, + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * mode: 'url', + * message: string, + * elicitationId: string, + * url: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['message', 'elicitationId', 'url']); + + /** @var \WP\McpSchema\Client\Tasks\TaskMetadata|null $task */ + $task = isset($data['task']) + ? (is_array($data['task']) + ? TaskMetadata::fromArray(self::asArray($data['task'])) + : $data['task']) + : null; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['message']), + self::asString($data['elicitationId']), + self::asString($data['url']), + $task, + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['mode'] = $this->mode; + $result['message'] = $this->message; + $result['elicitationId'] = $this->elicitationId; + $result['url'] = $this->url; + + return $result; + } + + /** + * @return 'url' + */ + public function getMode(): string + { + return $this->mode; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @return string + */ + public function getElicitationId(): string + { + return $this->elicitationId; + } + + /** + * @return string + */ + public function getUrl(): string + { + return $this->url; + } +} diff --git a/src/Client/Elicitation/ElicitResult.php b/src/Client/Elicitation/ElicitResult.php new file mode 100644 index 0000000..99ac805 --- /dev/null +++ b/src/Client/Elicitation/ElicitResult.php @@ -0,0 +1,119 @@ +|null + */ + protected ?array $content; + + /** + * @param 'accept'|'decline'|'cancel' $action @since 2025-06-18 + * @param array|null $_meta @since 2025-06-18 + * @param array|null $content @since 2025-06-18 + */ + public function __construct( + string $action, + ?array $_meta = null, + ?array $content = null + ) { + parent::__construct($_meta); + $this->action = $action; + $this->content = $content; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * action: 'accept'|'decline'|'cancel', + * content?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['action']); + + /** @var 'accept'|'decline'|'cancel' $action */ + $action = self::asString($data['action']); + + return new self( + $action, + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringMapOrNull($data['content'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['action'] = $this->action; + if ($this->content !== null) { + $result['content'] = $this->content; + } + + return $result; + } + + /** + * @return 'accept'|'decline'|'cancel' + */ + public function getAction(): string + { + return $this->action; + } + + /** + * @return array|null + */ + public function getContent(): ?array + { + return $this->content; + } +} diff --git a/src/Client/Elicitation/ElicitationCompleteNotification.php b/src/Client/Elicitation/ElicitationCompleteNotification.php new file mode 100644 index 0000000..d93e052 --- /dev/null +++ b/src/Client/Elicitation/ElicitationCompleteNotification.php @@ -0,0 +1,96 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/elicitation/complete', + * params: array|\WP\McpSchema\Client\Elicitation\ElicitationCompleteNotificationParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Client\Elicitation\ElicitationCompleteNotificationParams $params */ + $params = is_array($data['params']) + ? ElicitationCompleteNotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Client\Elicitation\ElicitationCompleteNotificationParams + */ + public function getTypedParams(): ElicitationCompleteNotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Client/Elicitation/ElicitationCompleteNotificationParams.php b/src/Client/Elicitation/ElicitationCompleteNotificationParams.php new file mode 100644 index 0000000..6e55534 --- /dev/null +++ b/src/Client/Elicitation/ElicitationCompleteNotificationParams.php @@ -0,0 +1,74 @@ +elicitationId = $elicitationId; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * elicitationId: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['elicitationId']); + + return new self( + self::asString($data['elicitationId']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['elicitationId'] = $this->elicitationId; + + return $result; + } + + /** + * @return string + */ + public function getElicitationId(): string + { + return $this->elicitationId; + } +} diff --git a/src/Client/Elicitation/Factory/ElicitRequestParamsFactory.php b/src/Client/Elicitation/Factory/ElicitRequestParamsFactory.php new file mode 100644 index 0000000..8b864e7 --- /dev/null +++ b/src/Client/Elicitation/Factory/ElicitRequestParamsFactory.php @@ -0,0 +1,79 @@ +> + */ + public const REGISTRY = [ + 'form' => ElicitRequestFormParams::class, + 'url' => ElicitRequestURLParams::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ElicitRequestParamsInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ElicitRequestParamsInterface + { + if (!isset($data['mode'])) { + throw new \InvalidArgumentException('Missing discriminator field: mode'); + } + + /** @var string $mode */ + $mode = $data['mode']; + if (!isset(self::REGISTRY[$mode])) { + throw new \InvalidArgumentException(sprintf( + "Unknown mode value '%s'. Valid values: %s", + $mode, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$mode]; + return $class::fromArray($data); + } + + /** + * Checks if a mode value is supported by this factory. + * + * @param string $mode + * @return bool + */ + public static function supports(string $mode): bool + { + return isset(self::REGISTRY[$mode]); + } + + /** + * Returns all supported mode values. + * + * @return array + */ + public static function modes(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Client/Elicitation/Factory/EnumSchemaFactory.php b/src/Client/Elicitation/Factory/EnumSchemaFactory.php new file mode 100644 index 0000000..ecc9f05 --- /dev/null +++ b/src/Client/Elicitation/Factory/EnumSchemaFactory.php @@ -0,0 +1,87 @@ + + */ + public const REGISTRY = [ + 'array' => MultiSelectEnumSchemaFactory::class, + 'string' => LegacyTitledEnumSchema::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return EnumSchemaInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): EnumSchemaInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + switch ($data['type']) { + case 'array': + return MultiSelectEnumSchemaFactory::fromArray($data); + case 'string': + if (isset($data['oneOf'])) { + return SingleSelectEnumSchemaFactory::fromArray($data); + } + elseif (isset($data['enumNames'])) { + return LegacyTitledEnumSchema::fromArray($data); + } + else { + return LegacyTitledEnumSchema::fromArray($data); + } + default: + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + is_scalar($data['type']) ? $data['type'] : gettype($data['type']), + implode(', ', array_keys(self::REGISTRY)) + )); + } + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Client/Elicitation/Factory/MultiSelectEnumSchemaFactory.php b/src/Client/Elicitation/Factory/MultiSelectEnumSchemaFactory.php new file mode 100644 index 0000000..294f2c0 --- /dev/null +++ b/src/Client/Elicitation/Factory/MultiSelectEnumSchemaFactory.php @@ -0,0 +1,74 @@ +> + */ + public const REGISTRY = [ + 'array' => UntitledMultiSelectEnumSchema::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return MultiSelectEnumSchemaInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): MultiSelectEnumSchemaInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + switch ($data['type']) { + case 'array': + return UntitledMultiSelectEnumSchema::fromArray($data); + default: + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + is_scalar($data['type']) ? $data['type'] : gettype($data['type']), + implode(', ', array_keys(self::REGISTRY)) + )); + } + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Client/Elicitation/Factory/PrimitiveSchemaDefinitionFactory.php b/src/Client/Elicitation/Factory/PrimitiveSchemaDefinitionFactory.php new file mode 100644 index 0000000..ee63269 --- /dev/null +++ b/src/Client/Elicitation/Factory/PrimitiveSchemaDefinitionFactory.php @@ -0,0 +1,100 @@ + + */ + public const REGISTRY = [ + 'number' => NumberSchema::class, + 'integer' => NumberSchema::class, + 'boolean' => BooleanSchema::class, + 'array' => EnumSchemaFactory::class, + 'string' => StringSchema::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return PrimitiveSchemaDefinitionInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): PrimitiveSchemaDefinitionInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + switch ($data['type']) { + case 'number': + return NumberSchema::fromArray($data); + case 'integer': + return NumberSchema::fromArray($data); + case 'boolean': + return BooleanSchema::fromArray($data); + case 'array': + return EnumSchemaFactory::fromArray($data); + case 'string': + if (isset($data['minLength'])) { + return StringSchema::fromArray($data); + } + elseif (isset($data['enum'])) { + return EnumSchemaFactory::fromArray($data); + } + else { + return StringSchema::fromArray($data); + } + default: + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + is_scalar($data['type']) ? $data['type'] : gettype($data['type']), + implode(', ', array_keys(self::REGISTRY)) + )); + } + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Client/Elicitation/Factory/SingleSelectEnumSchemaFactory.php b/src/Client/Elicitation/Factory/SingleSelectEnumSchemaFactory.php new file mode 100644 index 0000000..1a7a614 --- /dev/null +++ b/src/Client/Elicitation/Factory/SingleSelectEnumSchemaFactory.php @@ -0,0 +1,82 @@ +> + */ + public const REGISTRY = [ + 'string' => UntitledSingleSelectEnumSchema::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return SingleSelectEnumSchemaInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): SingleSelectEnumSchemaInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + switch ($data['type']) { + case 'string': + if (isset($data['enum'])) { + return UntitledSingleSelectEnumSchema::fromArray($data); + } + elseif (isset($data['oneOf'])) { + return TitledSingleSelectEnumSchema::fromArray($data); + } + else { + return UntitledSingleSelectEnumSchema::fromArray($data); + } + default: + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + is_scalar($data['type']) ? $data['type'] : gettype($data['type']), + implode(', ', array_keys(self::REGISTRY)) + )); + } + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Client/Elicitation/LegacyTitledEnumSchema.php b/src/Client/Elicitation/LegacyTitledEnumSchema.php new file mode 100644 index 0000000..05d4ba8 --- /dev/null +++ b/src/Client/Elicitation/LegacyTitledEnumSchema.php @@ -0,0 +1,198 @@ + + */ + protected array $enum; + + /** + * (Legacy) Display names for enum values. + * Non-standard according to JSON schema 2020-12. + * + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $enumNames; + + /** + * @since 2025-11-25 + * + * @var string|null + */ + protected ?string $default; + + /** + * @param array $enum @since 2025-11-25 + * @param string|null $title @since 2025-11-25 + * @param string|null $description @since 2025-11-25 + * @param array|null $enumNames @since 2025-11-25 + * @param string|null $default @since 2025-11-25 + */ + public function __construct( + array $enum, + ?string $title = null, + ?string $description = null, + ?array $enumNames = null, + ?string $default = null + ) { + $this->type = self::TYPE; + $this->enum = $enum; + $this->title = $title; + $this->description = $description; + $this->enumNames = $enumNames; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'string', + * title?: string|null, + * description?: string|null, + * enum: array, + * enumNames?: array|null, + * default?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['enum']); + + return new self( + self::asStringArray($data['enum']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringArrayOrNull($data['enumNames'] ?? null), + self::asStringOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + $result['enum'] = $this->enum; + if ($this->enumNames !== null) { + $result['enumNames'] = $this->enumNames; + } + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'string' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return array + */ + public function getEnum(): array + { + return $this->enum; + } + + /** + * @return array|null + */ + public function getEnumNames(): ?array + { + return $this->enumNames; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/NumberSchema.php b/src/Client/Elicitation/NumberSchema.php new file mode 100644 index 0000000..97207d0 --- /dev/null +++ b/src/Client/Elicitation/NumberSchema.php @@ -0,0 +1,196 @@ +type = $type; + $this->title = $title; + $this->description = $description; + $this->minimum = $minimum; + $this->maximum = $maximum; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'number'|'integer', + * title?: string|null, + * description?: string|null, + * minimum?: float|null, + * maximum?: float|null, + * default?: float|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['type']); + + /** @var 'number'|'integer' $type */ + $type = self::asString($data['type']); + + return new self( + $type, + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asFloatOrNull($data['minimum'] ?? null), + self::asFloatOrNull($data['maximum'] ?? null), + self::asFloatOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->minimum !== null) { + $result['minimum'] = $this->minimum; + } + if ($this->maximum !== null) { + $result['maximum'] = $this->maximum; + } + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'number'|'integer' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return float|null + */ + public function getMinimum(): ?float + { + return $this->minimum; + } + + /** + * @return float|null + */ + public function getMaximum(): ?float + { + return $this->maximum; + } + + /** + * @return float|null + */ + public function getDefault(): ?float + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/StringSchema.php b/src/Client/Elicitation/StringSchema.php new file mode 100644 index 0000000..f849d75 --- /dev/null +++ b/src/Client/Elicitation/StringSchema.php @@ -0,0 +1,221 @@ +type = self::TYPE; + $this->title = $title; + $this->description = $description; + $this->minLength = $minLength; + $this->maxLength = $maxLength; + $this->format = $format; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'string', + * title?: string|null, + * description?: string|null, + * minLength?: int|null, + * maxLength?: int|null, + * format?: 'email'|'uri'|'date'|'date-time'|null, + * default?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var 'email'|'uri'|'date'|'date-time'|null $format */ + $format = isset($data['format']) + ? self::asStringOrNull($data['format']) + : null; + + return new self( + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asIntOrNull($data['minLength'] ?? null), + self::asIntOrNull($data['maxLength'] ?? null), + $format, + self::asStringOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->minLength !== null) { + $result['minLength'] = $this->minLength; + } + if ($this->maxLength !== null) { + $result['maxLength'] = $this->maxLength; + } + if ($this->format !== null) { + $result['format'] = $this->format; + } + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'string' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return int|null + */ + public function getMinLength(): ?int + { + return $this->minLength; + } + + /** + * @return int|null + */ + public function getMaxLength(): ?int + { + return $this->maxLength; + } + + /** + * @return 'email'|'uri'|'date'|'date-time'|null + */ + public function getFormat(): ?string + { + return $this->format; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/TitledMultiSelectEnumSchema.php b/src/Client/Elicitation/TitledMultiSelectEnumSchema.php new file mode 100644 index 0000000..3686114 --- /dev/null +++ b/src/Client/Elicitation/TitledMultiSelectEnumSchema.php @@ -0,0 +1,234 @@ +|null + */ + protected ?array $default; + + /** + * @param \WP\McpSchema\Client\Elicitation\TitledMultiSelectEnumSchemaItems $items @since 2025-11-25 + * @param string|null $title @since 2025-11-25 + * @param string|null $description @since 2025-11-25 + * @param int|null $minItems @since 2025-11-25 + * @param int|null $maxItems @since 2025-11-25 + * @param array|null $default @since 2025-11-25 + */ + public function __construct( + TitledMultiSelectEnumSchemaItems $items, + ?string $title = null, + ?string $description = null, + ?int $minItems = null, + ?int $maxItems = null, + ?array $default = null + ) { + $this->type = self::TYPE; + $this->items = $items; + $this->title = $title; + $this->description = $description; + $this->minItems = $minItems; + $this->maxItems = $maxItems; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'array', + * title?: string|null, + * description?: string|null, + * minItems?: int|null, + * maxItems?: int|null, + * items: array|\WP\McpSchema\Client\Elicitation\TitledMultiSelectEnumSchemaItems, + * default?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['items']); + + /** @var \WP\McpSchema\Client\Elicitation\TitledMultiSelectEnumSchemaItems $items */ + $items = is_array($data['items']) + ? TitledMultiSelectEnumSchemaItems::fromArray(self::asArray($data['items'])) + : $data['items']; + + return new self( + $items, + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asIntOrNull($data['minItems'] ?? null), + self::asIntOrNull($data['maxItems'] ?? null), + self::asStringArrayOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->minItems !== null) { + $result['minItems'] = $this->minItems; + } + if ($this->maxItems !== null) { + $result['maxItems'] = $this->maxItems; + } + $result['items'] = $this->items->toArray(); + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'array' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return int|null + */ + public function getMinItems(): ?int + { + return $this->minItems; + } + + /** + * @return int|null + */ + public function getMaxItems(): ?int + { + return $this->maxItems; + } + + /** + * @return \WP\McpSchema\Client\Elicitation\TitledMultiSelectEnumSchemaItems + */ + public function getItems(): TitledMultiSelectEnumSchemaItems + { + return $this->items; + } + + /** + * @return array|null + */ + public function getDefault(): ?array + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/TitledMultiSelectEnumSchemaItems.php b/src/Client/Elicitation/TitledMultiSelectEnumSchemaItems.php new file mode 100644 index 0000000..1a4bc82 --- /dev/null +++ b/src/Client/Elicitation/TitledMultiSelectEnumSchemaItems.php @@ -0,0 +1,48 @@ + $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self(); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + return $result; + } +} diff --git a/src/Client/Elicitation/TitledSingleSelectEnumSchema.php b/src/Client/Elicitation/TitledSingleSelectEnumSchema.php new file mode 100644 index 0000000..3d2895b --- /dev/null +++ b/src/Client/Elicitation/TitledSingleSelectEnumSchema.php @@ -0,0 +1,182 @@ + + */ + protected array $oneOf; + + /** + * Optional default value. + * + * @since 2025-11-25 + * + * @var string|null + */ + protected ?string $default; + + /** + * @param array $oneOf @since 2025-11-25 + * @param string|null $title @since 2025-11-25 + * @param string|null $description @since 2025-11-25 + * @param string|null $default @since 2025-11-25 + */ + public function __construct( + array $oneOf, + ?string $title = null, + ?string $description = null, + ?string $default = null + ) { + $this->type = self::TYPE; + $this->oneOf = $oneOf; + $this->title = $title; + $this->description = $description; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'string', + * title?: string|null, + * description?: string|null, + * oneOf: array, + * default?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['oneOf']); + + /** @var array $oneOf */ + $oneOf = self::asArray($data['oneOf']); + + return new self( + $oneOf, + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + $result['oneOf'] = $this->oneOf; + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'string' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return array + */ + public function getOneOf(): array + { + return $this->oneOf; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/Union/ElicitRequestParamsInterface.php b/src/Client/Elicitation/Union/ElicitRequestParamsInterface.php new file mode 100644 index 0000000..ff631a8 --- /dev/null +++ b/src/Client/Elicitation/Union/ElicitRequestParamsInterface.php @@ -0,0 +1,26 @@ + + */ + public function toArray(): array; +} diff --git a/src/Client/Elicitation/Union/EnumSchemaInterface.php b/src/Client/Elicitation/Union/EnumSchemaInterface.php new file mode 100644 index 0000000..dff30f4 --- /dev/null +++ b/src/Client/Elicitation/Union/EnumSchemaInterface.php @@ -0,0 +1,21 @@ + + */ + public function toArray(): array; +} diff --git a/src/Client/Elicitation/Union/SingleSelectEnumSchemaInterface.php b/src/Client/Elicitation/Union/SingleSelectEnumSchemaInterface.php new file mode 100644 index 0000000..ee5051a --- /dev/null +++ b/src/Client/Elicitation/Union/SingleSelectEnumSchemaInterface.php @@ -0,0 +1,20 @@ +|null + */ + protected ?array $default; + + /** + * @param \WP\McpSchema\Client\Elicitation\UntitledMultiSelectEnumSchemaItems $items @since 2025-11-25 + * @param string|null $title @since 2025-11-25 + * @param string|null $description @since 2025-11-25 + * @param int|null $minItems @since 2025-11-25 + * @param int|null $maxItems @since 2025-11-25 + * @param array|null $default @since 2025-11-25 + */ + public function __construct( + UntitledMultiSelectEnumSchemaItems $items, + ?string $title = null, + ?string $description = null, + ?int $minItems = null, + ?int $maxItems = null, + ?array $default = null + ) { + $this->type = self::TYPE; + $this->items = $items; + $this->title = $title; + $this->description = $description; + $this->minItems = $minItems; + $this->maxItems = $maxItems; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'array', + * title?: string|null, + * description?: string|null, + * minItems?: int|null, + * maxItems?: int|null, + * items: array|\WP\McpSchema\Client\Elicitation\UntitledMultiSelectEnumSchemaItems, + * default?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['items']); + + /** @var \WP\McpSchema\Client\Elicitation\UntitledMultiSelectEnumSchemaItems $items */ + $items = is_array($data['items']) + ? UntitledMultiSelectEnumSchemaItems::fromArray(self::asArray($data['items'])) + : $data['items']; + + return new self( + $items, + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asIntOrNull($data['minItems'] ?? null), + self::asIntOrNull($data['maxItems'] ?? null), + self::asStringArrayOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->minItems !== null) { + $result['minItems'] = $this->minItems; + } + if ($this->maxItems !== null) { + $result['maxItems'] = $this->maxItems; + } + $result['items'] = $this->items->toArray(); + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'array' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return int|null + */ + public function getMinItems(): ?int + { + return $this->minItems; + } + + /** + * @return int|null + */ + public function getMaxItems(): ?int + { + return $this->maxItems; + } + + /** + * @return \WP\McpSchema\Client\Elicitation\UntitledMultiSelectEnumSchemaItems + */ + public function getItems(): UntitledMultiSelectEnumSchemaItems + { + return $this->items; + } + + /** + * @return array|null + */ + public function getDefault(): ?array + { + return $this->default; + } +} diff --git a/src/Client/Elicitation/UntitledMultiSelectEnumSchemaItems.php b/src/Client/Elicitation/UntitledMultiSelectEnumSchemaItems.php new file mode 100644 index 0000000..55ecfe1 --- /dev/null +++ b/src/Client/Elicitation/UntitledMultiSelectEnumSchemaItems.php @@ -0,0 +1,94 @@ + + */ + protected array $enum; + + /** + * @param array $enum + */ + public function __construct( + array $enum + ) { + $this->type = self::TYPE; + $this->enum = $enum; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'string', + * enum: array + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['enum']); + + return new self( + self::asStringArray($data['enum']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['enum'] = $this->enum; + + return $result; + } + + /** + * @return 'string' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return array + */ + public function getEnum(): array + { + return $this->enum; + } +} diff --git a/src/Client/Elicitation/UntitledSingleSelectEnumSchema.php b/src/Client/Elicitation/UntitledSingleSelectEnumSchema.php new file mode 100644 index 0000000..0d1414d --- /dev/null +++ b/src/Client/Elicitation/UntitledSingleSelectEnumSchema.php @@ -0,0 +1,179 @@ + + */ + protected array $enum; + + /** + * Optional default value. + * + * @since 2025-11-25 + * + * @var string|null + */ + protected ?string $default; + + /** + * @param array $enum @since 2025-11-25 + * @param string|null $title @since 2025-11-25 + * @param string|null $description @since 2025-11-25 + * @param string|null $default @since 2025-11-25 + */ + public function __construct( + array $enum, + ?string $title = null, + ?string $description = null, + ?string $default = null + ) { + $this->type = self::TYPE; + $this->enum = $enum; + $this->title = $title; + $this->description = $description; + $this->default = $default; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'string', + * title?: string|null, + * description?: string|null, + * enum: array, + * default?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['enum']); + + return new self( + self::asStringArray($data['enum']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['default'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->description !== null) { + $result['description'] = $this->description; + } + $result['enum'] = $this->enum; + if ($this->default !== null) { + $result['default'] = $this->default; + } + + return $result; + } + + /** + * @return 'string' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return array + */ + public function getEnum(): array + { + return $this->enum; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } +} diff --git a/src/Client/Lifecycle/ClientCapabilities.php b/src/Client/Lifecycle/ClientCapabilities.php new file mode 100644 index 0000000..2cd925d --- /dev/null +++ b/src/Client/Lifecycle/ClientCapabilities.php @@ -0,0 +1,209 @@ +|null + */ + protected ?array $experimental; + + /** + * Present if the client supports listing roots. + * + * @since 2024-11-05 + * + * @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesRoots|null + */ + protected ?ClientCapabilitiesRoots $roots; + + /** + * Present if the client supports sampling from an LLM. + * + * @since 2024-11-05 + * + * @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesSampling|null + */ + protected ?ClientCapabilitiesSampling $sampling; + + /** + * Present if the client supports elicitation from the server. + * + * @since 2025-06-18 + * + * @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesElicitation|null + */ + protected ?ClientCapabilitiesElicitation $elicitation; + + /** + * Present if the client supports task-augmented requests. + * + * @since 2025-11-25 + * + * @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesTasks|null + */ + protected ?ClientCapabilitiesTasks $tasks; + + /** + * @param array|null $experimental @since 2024-11-05 + * @param \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesRoots|null $roots @since 2024-11-05 + * @param \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesSampling|null $sampling @since 2024-11-05 + * @param \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesElicitation|null $elicitation @since 2025-06-18 + * @param \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesTasks|null $tasks @since 2025-11-25 + */ + public function __construct( + ?array $experimental = null, + ?ClientCapabilitiesRoots $roots = null, + ?ClientCapabilitiesSampling $sampling = null, + ?ClientCapabilitiesElicitation $elicitation = null, + ?ClientCapabilitiesTasks $tasks = null + ) { + $this->experimental = $experimental; + $this->roots = $roots; + $this->sampling = $sampling; + $this->elicitation = $elicitation; + $this->tasks = $tasks; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * experimental?: array|null, + * roots?: array|\WP\McpSchema\Client\Lifecycle\ClientCapabilitiesRoots|null, + * sampling?: array|\WP\McpSchema\Client\Lifecycle\ClientCapabilitiesSampling|null, + * elicitation?: array|\WP\McpSchema\Client\Lifecycle\ClientCapabilitiesElicitation|null, + * tasks?: array|\WP\McpSchema\Client\Lifecycle\ClientCapabilitiesTasks|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesRoots|null $roots */ + $roots = isset($data['roots']) + ? (is_array($data['roots']) + ? ClientCapabilitiesRoots::fromArray(self::asArray($data['roots'])) + : $data['roots']) + : null; + + /** @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesSampling|null $sampling */ + $sampling = isset($data['sampling']) + ? (is_array($data['sampling']) + ? ClientCapabilitiesSampling::fromArray(self::asArray($data['sampling'])) + : $data['sampling']) + : null; + + /** @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesElicitation|null $elicitation */ + $elicitation = isset($data['elicitation']) + ? (is_array($data['elicitation']) + ? ClientCapabilitiesElicitation::fromArray(self::asArray($data['elicitation'])) + : $data['elicitation']) + : null; + + /** @var \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesTasks|null $tasks */ + $tasks = isset($data['tasks']) + ? (is_array($data['tasks']) + ? ClientCapabilitiesTasks::fromArray(self::asArray($data['tasks'])) + : $data['tasks']) + : null; + + return new self( + self::asObjectMapOrNull($data['experimental'] ?? null), + $roots, + $sampling, + $elicitation, + $tasks + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->experimental !== null) { + $result['experimental'] = $this->experimental; + } + if ($this->roots !== null) { + $result['roots'] = $this->roots->toArray(); + } + if ($this->sampling !== null) { + $result['sampling'] = $this->sampling->toArray(); + } + if ($this->elicitation !== null) { + $result['elicitation'] = $this->elicitation->toArray(); + } + if ($this->tasks !== null) { + $result['tasks'] = $this->tasks->toArray(); + } + + return $result; + } + + /** + * @return array|null + */ + public function getExperimental(): ?array + { + return $this->experimental; + } + + /** + * @return \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesRoots|null + */ + public function getRoots(): ?ClientCapabilitiesRoots + { + return $this->roots; + } + + /** + * @return \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesSampling|null + */ + public function getSampling(): ?ClientCapabilitiesSampling + { + return $this->sampling; + } + + /** + * @return \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesElicitation|null + */ + public function getElicitation(): ?ClientCapabilitiesElicitation + { + return $this->elicitation; + } + + /** + * @return \WP\McpSchema\Client\Lifecycle\ClientCapabilitiesTasks|null + */ + public function getTasks(): ?ClientCapabilitiesTasks + { + return $this->tasks; + } +} diff --git a/src/Client/Lifecycle/ClientCapabilitiesElicitation.php b/src/Client/Lifecycle/ClientCapabilitiesElicitation.php new file mode 100644 index 0000000..f0129b3 --- /dev/null +++ b/src/Client/Lifecycle/ClientCapabilitiesElicitation.php @@ -0,0 +1,95 @@ +form = $form; + $this->url = $url; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * form?: object|null, + * url?: object|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asObjectOrNull($data['form'] ?? null), + self::asObjectOrNull($data['url'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->form !== null) { + $result['form'] = $this->form; + } + if ($this->url !== null) { + $result['url'] = $this->url; + } + + return $result; + } + + /** + * @return object|null + */ + public function getForm(): ?object + { + return $this->form; + } + + /** + * @return object|null + */ + public function getUrl(): ?object + { + return $this->url; + } +} diff --git a/src/Client/Lifecycle/ClientCapabilitiesRoots.php b/src/Client/Lifecycle/ClientCapabilitiesRoots.php new file mode 100644 index 0000000..df60d68 --- /dev/null +++ b/src/Client/Lifecycle/ClientCapabilitiesRoots.php @@ -0,0 +1,76 @@ +listChanged = $listChanged; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * listChanged?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asBoolOrNull($data['listChanged'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->listChanged !== null) { + $result['listChanged'] = $this->listChanged; + } + + return $result; + } + + /** + * @return bool|null + */ + public function getListChanged(): ?bool + { + return $this->listChanged; + } +} diff --git a/src/Client/Lifecycle/ClientCapabilitiesSampling.php b/src/Client/Lifecycle/ClientCapabilitiesSampling.php new file mode 100644 index 0000000..6176bda --- /dev/null +++ b/src/Client/Lifecycle/ClientCapabilitiesSampling.php @@ -0,0 +1,99 @@ +context = $context; + $this->tools = $tools; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * context?: object|null, + * tools?: object|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asObjectOrNull($data['context'] ?? null), + self::asObjectOrNull($data['tools'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->context !== null) { + $result['context'] = $this->context; + } + if ($this->tools !== null) { + $result['tools'] = $this->tools; + } + + return $result; + } + + /** + * @return object|null + */ + public function getContext(): ?object + { + return $this->context; + } + + /** + * @return object|null + */ + public function getTools(): ?object + { + return $this->tools; + } +} diff --git a/src/Client/Lifecycle/ClientCapabilitiesTasks.php b/src/Client/Lifecycle/ClientCapabilitiesTasks.php new file mode 100644 index 0000000..926c78f --- /dev/null +++ b/src/Client/Lifecycle/ClientCapabilitiesTasks.php @@ -0,0 +1,99 @@ +list = $list; + $this->cancel = $cancel; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * list?: object|null, + * cancel?: object|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asObjectOrNull($data['list'] ?? null), + self::asObjectOrNull($data['cancel'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->list !== null) { + $result['list'] = $this->list; + } + if ($this->cancel !== null) { + $result['cancel'] = $this->cancel; + } + + return $result; + } + + /** + * @return object|null + */ + public function getList(): ?object + { + return $this->list; + } + + /** + * @return object|null + */ + public function getCancel(): ?object + { + return $this->cancel; + } +} diff --git a/src/Client/Lifecycle/Union/ClientResultInterface.php b/src/Client/Lifecycle/Union/ClientResultInterface.php new file mode 100644 index 0000000..55673a2 --- /dev/null +++ b/src/Client/Lifecycle/Union/ClientResultInterface.php @@ -0,0 +1,30 @@ + + */ + public function toArray(): array; +} diff --git a/src/Client/Roots/ListRootsRequest.php b/src/Client/Roots/ListRootsRequest.php new file mode 100644 index 0000000..0c2d435 --- /dev/null +++ b/src/Client/Roots/ListRootsRequest.php @@ -0,0 +1,115 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'roots/list', + * params?: array|\WP\McpSchema\Common\JsonRpc\RequestParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? RequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\RequestParams|null + */ + public function getTypedParams(): ?RequestParams + { + return $this->typedParams; + } +} diff --git a/src/Client/Roots/ListRootsResult.php b/src/Client/Roots/ListRootsResult.php new file mode 100644 index 0000000..454e719 --- /dev/null +++ b/src/Client/Roots/ListRootsResult.php @@ -0,0 +1,95 @@ + + */ + protected array $roots; + + /** + * @param array<\WP\McpSchema\Client\Roots\Root> $roots @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $roots, + ?array $_meta = null + ) { + parent::__construct($_meta); + $this->roots = $roots; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * roots: array|\WP\McpSchema\Client\Roots\Root> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['roots']); + + /** @var array<\WP\McpSchema\Client\Roots\Root> $roots */ + $roots = array_map( + static fn($item) => is_array($item) + ? Root::fromArray($item) + : $item, + self::asArray($data['roots']) + ); + + return new self( + $roots, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['roots'] = array_map(static fn($item) => $item->toArray(), $this->roots); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Client\Roots\Root> + */ + public function getRoots(): array + { + return $this->roots; + } +} diff --git a/src/Client/Roots/Root.php b/src/Client/Roots/Root.php new file mode 100644 index 0000000..264c686 --- /dev/null +++ b/src/Client/Roots/Root.php @@ -0,0 +1,135 @@ +|null + */ + protected ?array $_meta; + + /** + * @param string $uri @since 2024-11-05 + * @param string|null $name @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + string $uri, + ?string $name = null, + ?array $_meta = null + ) { + $this->uri = $uri; + $this->name = $name; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * uri: string, + * name?: string|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + return new self( + self::asString($data['uri']), + self::asStringOrNull($data['name'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['uri'] = $this->uri; + if ($this->name !== null) { + $result['name'] = $this->name; + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Client/Roots/RootsListChangedNotification.php b/src/Client/Roots/RootsListChangedNotification.php new file mode 100644 index 0000000..7854c49 --- /dev/null +++ b/src/Client/Roots/RootsListChangedNotification.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/roots/list_changed', + * params?: array|\WP\McpSchema\Common\JsonRpc\NotificationParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\NotificationParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? NotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\NotificationParams|null + */ + public function getTypedParams(): ?NotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Client/Sampling/CreateMessageRequest.php b/src/Client/Sampling/CreateMessageRequest.php new file mode 100644 index 0000000..7533964 --- /dev/null +++ b/src/Client/Sampling/CreateMessageRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'sampling/createMessage', + * params: array|\WP\McpSchema\Client\Sampling\CreateMessageRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Client\Sampling\CreateMessageRequestParams $params */ + $params = is_array($data['params']) + ? CreateMessageRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Client\Sampling\CreateMessageRequestParams + */ + public function getTypedParams(): CreateMessageRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Client/Sampling/CreateMessageRequestParams.php b/src/Client/Sampling/CreateMessageRequestParams.php new file mode 100644 index 0000000..831cf4d --- /dev/null +++ b/src/Client/Sampling/CreateMessageRequestParams.php @@ -0,0 +1,369 @@ + + */ + protected array $messages; + + /** + * The server's preferences for which model to select. The client MAY ignore these preferences. + * + * @since 2025-11-25 + * + * @var \WP\McpSchema\Client\Sampling\ModelPreferences|null + */ + protected ?ModelPreferences $modelPreferences; + + /** + * An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + * + * @since 2025-11-25 + * + * @var string|null + */ + protected ?string $systemPrompt; + + /** + * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + * The client MAY ignore this request. + * + * Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + * declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. + * + * @since 2025-11-25 + * + * @var 'none'|'thisServer'|'allServers'|null + */ + protected ?string $includeContext; + + /** + * @since 2025-11-25 + * + * @var float|null + */ + protected ?float $temperature; + + /** + * The requested maximum number of tokens to sample (to prevent runaway completions). + * + * The client MAY choose to sample fewer tokens than the requested maximum. + * + * @since 2025-11-25 + * + * @var float + */ + protected float $maxTokens; + + /** + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $stopSequences; + + /** + * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + * + * @since 2025-11-25 + * + * @var object|null + */ + protected ?object $metadata; + + /** + * Tools that the model may use during generation. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + * + * @since 2025-11-25 + * + * @var array<\WP\McpSchema\Server\Tools\Tool>|null + */ + protected ?array $tools; + + /** + * Controls how the model uses tools. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + * Default is `{ mode: "auto" }`. + * + * @since 2025-11-25 + * + * @var \WP\McpSchema\Client\Sampling\ToolChoice|null + */ + protected ?ToolChoice $toolChoice; + + /** + * @param array<\WP\McpSchema\Client\Sampling\SamplingMessage> $messages @since 2025-11-25 + * @param float $maxTokens @since 2025-11-25 + * @param \WP\McpSchema\Client\Tasks\TaskMetadata|null $task @since 2025-11-25 + * @param \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta @since 2025-11-25 + * @param \WP\McpSchema\Client\Sampling\ModelPreferences|null $modelPreferences @since 2025-11-25 + * @param string|null $systemPrompt @since 2025-11-25 + * @param 'none'|'thisServer'|'allServers'|null $includeContext @since 2025-11-25 + * @param float|null $temperature @since 2025-11-25 + * @param array|null $stopSequences @since 2025-11-25 + * @param object|null $metadata @since 2025-11-25 + * @param array<\WP\McpSchema\Server\Tools\Tool>|null $tools @since 2025-11-25 + * @param \WP\McpSchema\Client\Sampling\ToolChoice|null $toolChoice @since 2025-11-25 + */ + public function __construct( + array $messages, + float $maxTokens, + ?TaskMetadata $task = null, + ?RequestParamsMeta $_meta = null, + ?ModelPreferences $modelPreferences = null, + ?string $systemPrompt = null, + ?string $includeContext = null, + ?float $temperature = null, + ?array $stopSequences = null, + ?object $metadata = null, + ?array $tools = null, + ?ToolChoice $toolChoice = null + ) { + parent::__construct($_meta, $task); + $this->messages = $messages; + $this->maxTokens = $maxTokens; + $this->modelPreferences = $modelPreferences; + $this->systemPrompt = $systemPrompt; + $this->includeContext = $includeContext; + $this->temperature = $temperature; + $this->stopSequences = $stopSequences; + $this->metadata = $metadata; + $this->tools = $tools; + $this->toolChoice = $toolChoice; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * task?: array|\WP\McpSchema\Client\Tasks\TaskMetadata|null, + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * messages: array|\WP\McpSchema\Client\Sampling\SamplingMessage>, + * modelPreferences?: array|\WP\McpSchema\Client\Sampling\ModelPreferences|null, + * systemPrompt?: string|null, + * includeContext?: 'none'|'thisServer'|'allServers'|null, + * temperature?: float|null, + * maxTokens: float, + * stopSequences?: array|null, + * metadata?: object|null, + * tools?: array|\WP\McpSchema\Server\Tools\Tool>|null, + * toolChoice?: array|\WP\McpSchema\Client\Sampling\ToolChoice|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['messages', 'maxTokens']); + + /** @var array<\WP\McpSchema\Client\Sampling\SamplingMessage> $messages */ + $messages = array_map( + static fn($item) => is_array($item) + ? SamplingMessage::fromArray($item) + : $item, + self::asArray($data['messages']) + ); + + /** @var \WP\McpSchema\Client\Tasks\TaskMetadata|null $task */ + $task = isset($data['task']) + ? (is_array($data['task']) + ? TaskMetadata::fromArray(self::asArray($data['task'])) + : $data['task']) + : null; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + /** @var \WP\McpSchema\Client\Sampling\ModelPreferences|null $modelPreferences */ + $modelPreferences = isset($data['modelPreferences']) + ? (is_array($data['modelPreferences']) + ? ModelPreferences::fromArray(self::asArray($data['modelPreferences'])) + : $data['modelPreferences']) + : null; + + /** @var 'none'|'thisServer'|'allServers'|null $includeContext */ + $includeContext = isset($data['includeContext']) + ? self::asStringOrNull($data['includeContext']) + : null; + + /** @var array<\WP\McpSchema\Server\Tools\Tool>|null $tools */ + $tools = isset($data['tools']) + ? array_map( + static fn($item) => is_array($item) + ? Tool::fromArray($item) + : $item, + self::asArray($data['tools']) + ) + : null; + + /** @var \WP\McpSchema\Client\Sampling\ToolChoice|null $toolChoice */ + $toolChoice = isset($data['toolChoice']) + ? (is_array($data['toolChoice']) + ? ToolChoice::fromArray(self::asArray($data['toolChoice'])) + : $data['toolChoice']) + : null; + + return new self( + $messages, + self::asFloat($data['maxTokens']), + $task, + $_meta, + $modelPreferences, + self::asStringOrNull($data['systemPrompt'] ?? null), + $includeContext, + self::asFloatOrNull($data['temperature'] ?? null), + self::asStringArrayOrNull($data['stopSequences'] ?? null), + self::asObjectOrNull($data['metadata'] ?? null), + $tools, + $toolChoice + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['messages'] = array_map(static fn($item) => $item->toArray(), $this->messages); + if ($this->modelPreferences !== null) { + $result['modelPreferences'] = $this->modelPreferences->toArray(); + } + if ($this->systemPrompt !== null) { + $result['systemPrompt'] = $this->systemPrompt; + } + if ($this->includeContext !== null) { + $result['includeContext'] = $this->includeContext; + } + if ($this->temperature !== null) { + $result['temperature'] = $this->temperature; + } + $result['maxTokens'] = $this->maxTokens; + if ($this->stopSequences !== null) { + $result['stopSequences'] = $this->stopSequences; + } + if ($this->metadata !== null) { + $result['metadata'] = $this->metadata; + } + if ($this->tools !== null) { + $result['tools'] = array_map(static fn($item) => $item->toArray(), $this->tools); + } + if ($this->toolChoice !== null) { + $result['toolChoice'] = $this->toolChoice->toArray(); + } + + return $result; + } + + /** + * @return array<\WP\McpSchema\Client\Sampling\SamplingMessage> + */ + public function getMessages(): array + { + return $this->messages; + } + + /** + * @return \WP\McpSchema\Client\Sampling\ModelPreferences|null + */ + public function getModelPreferences(): ?ModelPreferences + { + return $this->modelPreferences; + } + + /** + * @return string|null + */ + public function getSystemPrompt(): ?string + { + return $this->systemPrompt; + } + + /** + * @return 'none'|'thisServer'|'allServers'|null + */ + public function getIncludeContext(): ?string + { + return $this->includeContext; + } + + /** + * @return float|null + */ + public function getTemperature(): ?float + { + return $this->temperature; + } + + /** + * @return float + */ + public function getMaxTokens(): float + { + return $this->maxTokens; + } + + /** + * @return array|null + */ + public function getStopSequences(): ?array + { + return $this->stopSequences; + } + + /** + * @return object|null + */ + public function getMetadata(): ?object + { + return $this->metadata; + } + + /** + * @return array<\WP\McpSchema\Server\Tools\Tool>|null + */ + public function getTools(): ?array + { + return $this->tools; + } + + /** + * @return \WP\McpSchema\Client\Sampling\ToolChoice|null + */ + public function getToolChoice(): ?ToolChoice + { + return $this->toolChoice; + } +} diff --git a/src/Client/Sampling/CreateMessageResult.php b/src/Client/Sampling/CreateMessageResult.php new file mode 100644 index 0000000..e4212fb --- /dev/null +++ b/src/Client/Sampling/CreateMessageResult.php @@ -0,0 +1,181 @@ + + */ + protected array $content; + + /** + * @param string $model @since 2024-11-05 + * @param 'user'|'assistant' $role @since 2024-11-05 + * @param array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> $content @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + * @param "endTurn"|"stopSequence"|"maxTokens"|"toolUse"|string|null $stopReason @since 2024-11-05 + */ + public function __construct( + string $model, + string $role, + array $content, + ?array $_meta = null, + $stopReason = null + ) { + parent::__construct($_meta); + $this->model = $model; + $this->role = $role; + $this->content = $content; + $this->stopReason = $stopReason; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * model: string, + * stopReason?: "endTurn"|"stopSequence"|"maxTokens"|"toolUse"|string|null, + * role: 'user'|'assistant', + * content: array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['model', 'role', 'content']); + + /** @var 'user'|'assistant' $role */ + $role = self::asString($data['role']); + + /** @var array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> $content */ + $content = array_map( + static fn($item) => is_array($item) + ? SamplingMessageContentBlockFactory::fromArray($item) + : $item, + self::asArray($data['content']) + ); + + /** @var "endTurn"|"stopSequence"|"maxTokens"|"toolUse"|string|null $stopReason */ + $stopReason = isset($data['stopReason']) + ? $data['stopReason'] + : null; + + return new self( + self::asString($data['model']), + $role, + $content, + self::asArrayOrNull($data['_meta'] ?? null), + $stopReason + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['model'] = $this->model; + if ($this->stopReason !== null) { + $result['stopReason'] = $this->stopReason; + } + $result['role'] = $this->role; + $result['content'] = array_map(static fn($item) => (is_object($item) && method_exists($item, 'toArray')) ? $item->toArray() : $item, $this->content); + + return $result; + } + + /** + * @return string + */ + public function getModel(): string + { + return $this->model; + } + + /** + * @return "endTurn"|"stopSequence"|"maxTokens"|"toolUse"|string|null + */ + public function getStopReason() + { + return $this->stopReason; + } + + /** + * @return 'user'|'assistant' + */ + public function getRole(): string + { + return $this->role; + } + + /** + * @return array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> + */ + public function getContent(): array + { + return $this->content; + } +} diff --git a/src/Client/Sampling/ModelHint.php b/src/Client/Sampling/ModelHint.php new file mode 100644 index 0000000..e071098 --- /dev/null +++ b/src/Client/Sampling/ModelHint.php @@ -0,0 +1,91 @@ +name = $name; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['name'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->name !== null) { + $result['name'] = $this->name; + } + + return $result; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } +} diff --git a/src/Client/Sampling/ModelPreferences.php b/src/Client/Sampling/ModelPreferences.php new file mode 100644 index 0000000..e224e82 --- /dev/null +++ b/src/Client/Sampling/ModelPreferences.php @@ -0,0 +1,188 @@ +|null + */ + protected ?array $hints; + + /** + * How much to prioritize cost when selecting a model. A value of 0 means cost + * is not important, while a value of 1 means cost is the most important + * factor. + * + * @since 2024-11-05 + * + * @var float|null + */ + protected ?float $costPriority; + + /** + * How much to prioritize sampling speed (latency) when selecting a model. A + * value of 0 means speed is not important, while a value of 1 means speed is + * the most important factor. + * + * @since 2024-11-05 + * + * @var float|null + */ + protected ?float $speedPriority; + + /** + * How much to prioritize intelligence and capabilities when selecting a + * model. A value of 0 means intelligence is not important, while a value of 1 + * means intelligence is the most important factor. + * + * @since 2024-11-05 + * + * @var float|null + */ + protected ?float $intelligencePriority; + + /** + * @param array<\WP\McpSchema\Client\Sampling\ModelHint>|null $hints @since 2024-11-05 + * @param float|null $costPriority @since 2024-11-05 + * @param float|null $speedPriority @since 2024-11-05 + * @param float|null $intelligencePriority @since 2024-11-05 + */ + public function __construct( + ?array $hints = null, + ?float $costPriority = null, + ?float $speedPriority = null, + ?float $intelligencePriority = null + ) { + $this->hints = $hints; + $this->costPriority = $costPriority; + $this->speedPriority = $speedPriority; + $this->intelligencePriority = $intelligencePriority; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * hints?: array|\WP\McpSchema\Client\Sampling\ModelHint>|null, + * costPriority?: float|null, + * speedPriority?: float|null, + * intelligencePriority?: float|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var array<\WP\McpSchema\Client\Sampling\ModelHint>|null $hints */ + $hints = isset($data['hints']) + ? array_map( + static fn($item) => is_array($item) + ? ModelHint::fromArray($item) + : $item, + self::asArray($data['hints']) + ) + : null; + + return new self( + $hints, + self::asFloatOrNull($data['costPriority'] ?? null), + self::asFloatOrNull($data['speedPriority'] ?? null), + self::asFloatOrNull($data['intelligencePriority'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->hints !== null) { + $result['hints'] = array_map(static fn($item) => $item->toArray(), $this->hints); + } + if ($this->costPriority !== null) { + $result['costPriority'] = $this->costPriority; + } + if ($this->speedPriority !== null) { + $result['speedPriority'] = $this->speedPriority; + } + if ($this->intelligencePriority !== null) { + $result['intelligencePriority'] = $this->intelligencePriority; + } + + return $result; + } + + /** + * @return array<\WP\McpSchema\Client\Sampling\ModelHint>|null + */ + public function getHints(): ?array + { + return $this->hints; + } + + /** + * @return float|null + */ + public function getCostPriority(): ?float + { + return $this->costPriority; + } + + /** + * @return float|null + */ + public function getSpeedPriority(): ?float + { + return $this->speedPriority; + } + + /** + * @return float|null + */ + public function getIntelligencePriority(): ?float + { + return $this->intelligencePriority; + } +} diff --git a/src/Client/Sampling/SamplingMessage.php b/src/Client/Sampling/SamplingMessage.php new file mode 100644 index 0000000..d2076f2 --- /dev/null +++ b/src/Client/Sampling/SamplingMessage.php @@ -0,0 +1,137 @@ + + */ + protected array $content; + + /** + * See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + * + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $_meta; + + /** + * @param 'user'|'assistant' $role @since 2024-11-05 + * @param array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> $content @since 2024-11-05 + * @param array|null $_meta @since 2025-11-25 + */ + public function __construct( + string $role, + array $content, + ?array $_meta = null + ) { + $this->role = $role; + $this->content = $content; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * role: 'user'|'assistant', + * content: array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface>, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['role', 'content']); + + /** @var 'user'|'assistant' $role */ + $role = self::asString($data['role']); + + /** @var array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> $content */ + $content = array_map( + static fn($item) => is_array($item) + ? SamplingMessageContentBlockFactory::fromArray($item) + : $item, + self::asArray($data['content']) + ); + + return new self( + $role, + $content, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['role'] = $this->role; + $result['content'] = array_map(static fn($item) => (is_object($item) && method_exists($item, 'toArray')) ? $item->toArray() : $item, $this->content); + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'user'|'assistant' + */ + public function getRole(): string + { + return $this->role; + } + + /** + * @return array<\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface|\WP\McpSchema\Common\Protocol\Union\SamplingMessageContentBlockInterface> + */ + public function getContent(): array + { + return $this->content; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Client/Sampling/ToolChoice.php b/src/Client/Sampling/ToolChoice.php new file mode 100644 index 0000000..08550c2 --- /dev/null +++ b/src/Client/Sampling/ToolChoice.php @@ -0,0 +1,88 @@ +mode = $mode; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * mode?: 'auto'|'required'|'none'|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var 'auto'|'required'|'none'|null $mode */ + $mode = isset($data['mode']) + ? self::asStringOrNull($data['mode']) + : null; + + return new self( + $mode + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->mode !== null) { + $result['mode'] = $this->mode; + } + + return $result; + } + + /** + * @return 'auto'|'required'|'none'|null + */ + public function getMode(): ?string + { + return $this->mode; + } +} diff --git a/src/Client/Sampling/ToolResultContent.php b/src/Client/Sampling/ToolResultContent.php new file mode 100644 index 0000000..a6cacc2 --- /dev/null +++ b/src/Client/Sampling/ToolResultContent.php @@ -0,0 +1,225 @@ + + */ + protected array $content; + + /** + * An optional structured result object. + * + * If the tool defined an outputSchema, this SHOULD conform to that schema. + * + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $structuredContent; + + /** + * Whether the tool use resulted in an error. + * + * If true, the content typically describes the error that occurred. + * Default: false + * + * @since 2025-11-25 + * + * @var bool|null + */ + protected ?bool $isError; + + /** + * Optional metadata about the tool result. Clients SHOULD preserve this field when + * including tool results in subsequent sampling requests to enable caching optimizations. + * + * See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + * + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $_meta; + + /** + * @param string $toolUseId @since 2025-11-25 + * @param array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> $content @since 2025-11-25 + * @param array|null $structuredContent @since 2025-11-25 + * @param bool|null $isError @since 2025-11-25 + * @param array|null $_meta @since 2025-11-25 + */ + public function __construct( + string $toolUseId, + array $content, + ?array $structuredContent = null, + ?bool $isError = null, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->toolUseId = $toolUseId; + $this->content = $content; + $this->structuredContent = $structuredContent; + $this->isError = $isError; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'tool_result', + * toolUseId: string, + * content: array|\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface>, + * structuredContent?: array|null, + * isError?: bool|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['toolUseId', 'content']); + + /** @var array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> $content */ + $content = array_map( + static fn($item) => is_array($item) + ? ContentBlockFactory::fromArray($item) + : $item, + self::asArray($data['content']) + ); + + return new self( + self::asString($data['toolUseId']), + $content, + self::asArrayOrNull($data['structuredContent'] ?? null), + self::asBoolOrNull($data['isError'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['toolUseId'] = $this->toolUseId; + $result['content'] = array_map(static fn($item) => $item->toArray(), $this->content); + if ($this->structuredContent !== null) { + $result['structuredContent'] = $this->structuredContent; + } + if ($this->isError !== null) { + $result['isError'] = $this->isError; + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'tool_result' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getToolUseId(): string + { + return $this->toolUseId; + } + + /** + * @return array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> + */ + public function getContent(): array + { + return $this->content; + } + + /** + * @return array|null + */ + public function getStructuredContent(): ?array + { + return $this->structuredContent; + } + + /** + * @return bool|null + */ + public function getIsError(): ?bool + { + return $this->isError; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Client/Sampling/ToolUseContent.php b/src/Client/Sampling/ToolUseContent.php new file mode 100644 index 0000000..76b1796 --- /dev/null +++ b/src/Client/Sampling/ToolUseContent.php @@ -0,0 +1,180 @@ + + */ + protected array $input; + + /** + * Optional metadata about the tool use. Clients SHOULD preserve this field when + * including tool uses in subsequent sampling requests to enable caching optimizations. + * + * See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + * + * @since 2025-11-25 + * + * @var array|null + */ + protected ?array $_meta; + + /** + * @param string $id @since 2025-11-25 + * @param string $name @since 2025-11-25 + * @param array $input @since 2025-11-25 + * @param array|null $_meta @since 2025-11-25 + */ + public function __construct( + string $id, + string $name, + array $input, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->id = $id; + $this->name = $name; + $this->input = $input; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'tool_use', + * id: string, + * name: string, + * input: array, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['id', 'name', 'input']); + + return new self( + self::asString($data['id']), + self::asString($data['name']), + self::asArray($data['input']), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['id'] = $this->id; + $result['name'] = $this->name; + $result['input'] = $this->input; + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'tool_use' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getInput(): array + { + return $this->input; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Client/Tasks/CreateTaskResult.php b/src/Client/Tasks/CreateTaskResult.php new file mode 100644 index 0000000..12b4eaf --- /dev/null +++ b/src/Client/Tasks/CreateTaskResult.php @@ -0,0 +1,88 @@ +|null $_meta @since 2025-11-25 + */ + public function __construct( + Task $task, + ?array $_meta = null + ) { + parent::__construct($_meta); + $this->task = $task; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * task: array|\WP\McpSchema\Client\Tasks\Task + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['task']); + + /** @var \WP\McpSchema\Client\Tasks\Task $task */ + $task = is_array($data['task']) + ? Task::fromArray(self::asArray($data['task'])) + : $data['task']; + + return new self( + $task, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['task'] = $this->task->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Client\Tasks\Task + */ + public function getTask(): Task + { + return $this->task; + } +} diff --git a/src/Client/Tasks/Enum/TaskStatus.php b/src/Client/Tasks/Enum/TaskStatus.php new file mode 100644 index 0000000..705cb7a --- /dev/null +++ b/src/Client/Tasks/Enum/TaskStatus.php @@ -0,0 +1,64 @@ +taskId = $taskId; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskId: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId']); + + return new self( + self::asString($data['taskId']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['taskId'] = $this->taskId; + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } +} diff --git a/src/Client/Tasks/Task.php b/src/Client/Tasks/Task.php new file mode 100644 index 0000000..2fd542f --- /dev/null +++ b/src/Client/Tasks/Task.php @@ -0,0 +1,231 @@ +taskId = $taskId; + $this->status = $status; + $this->createdAt = $createdAt; + $this->lastUpdatedAt = $lastUpdatedAt; + $this->ttl = $ttl; + $this->statusMessage = $statusMessage; + $this->pollInterval = $pollInterval; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskId: string, + * status: 'working'|'input_required'|'completed'|'failed'|'cancelled', + * statusMessage?: string|null, + * createdAt: string, + * lastUpdatedAt: string, + * ttl: int|null, + * pollInterval?: int|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId', 'status', 'createdAt', 'lastUpdatedAt', 'ttl']); + + /** @var 'working'|'input_required'|'completed'|'failed'|'cancelled' $status */ + $status = self::asString($data['status']); + + return new self( + self::asString($data['taskId']), + $status, + self::asString($data['createdAt']), + self::asString($data['lastUpdatedAt']), + self::asInt($data['ttl']), + self::asStringOrNull($data['statusMessage'] ?? null), + self::asIntOrNull($data['pollInterval'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['taskId'] = $this->taskId; + $result['status'] = $this->status; + if ($this->statusMessage !== null) { + $result['statusMessage'] = $this->statusMessage; + } + $result['createdAt'] = $this->createdAt; + $result['lastUpdatedAt'] = $this->lastUpdatedAt; + if ($this->ttl !== null) { + $result['ttl'] = $this->ttl; + } + if ($this->pollInterval !== null) { + $result['pollInterval'] = $this->pollInterval; + } + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } + + /** + * @return 'working'|'input_required'|'completed'|'failed'|'cancelled' + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return string|null + */ + public function getStatusMessage(): ?string + { + return $this->statusMessage; + } + + /** + * @return string + */ + public function getCreatedAt(): string + { + return $this->createdAt; + } + + /** + * @return string + */ + public function getLastUpdatedAt(): string + { + return $this->lastUpdatedAt; + } + + /** + * @return int|null + */ + public function getTtl(): ?int + { + return $this->ttl; + } + + /** + * @return int|null + */ + public function getPollInterval(): ?int + { + return $this->pollInterval; + } +} diff --git a/src/Client/Tasks/TaskMetadata.php b/src/Client/Tasks/TaskMetadata.php new file mode 100644 index 0000000..3f923bc --- /dev/null +++ b/src/Client/Tasks/TaskMetadata.php @@ -0,0 +1,81 @@ +ttl = $ttl; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * ttl?: int|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asIntOrNull($data['ttl'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->ttl !== null) { + $result['ttl'] = $this->ttl; + } + + return $result; + } + + /** + * @return int|null + */ + public function getTtl(): ?int + { + return $this->ttl; + } +} diff --git a/src/Common/AbstractDataTransferObject.php b/src/Common/AbstractDataTransferObject.php new file mode 100644 index 0000000..512ff07 --- /dev/null +++ b/src/Common/AbstractDataTransferObject.php @@ -0,0 +1,51 @@ + $data + * @return static + */ + abstract public static function fromArray(array $data): self; + + /** + * Converts the instance to an array. + * + * @return array + */ + abstract public function toArray(): array; + + /** + * Converts the instance to JSON. + * + * @return string + */ + public function toJson(): string + { + return json_encode($this->toArray(), JSON_THROW_ON_ERROR); + } + + /** + * Creates an instance from JSON. + * + * @param string $json + * @return static + */ + public static function fromJson(string $json): self + { + /** @var array $data */ + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + return static::fromArray($data); + } +} diff --git a/src/Common/AbstractEnum.php b/src/Common/AbstractEnum.php new file mode 100644 index 0000000..8a6efe4 --- /dev/null +++ b/src/Common/AbstractEnum.php @@ -0,0 +1,115 @@ + */ + private static array $instances = []; + + /** + * @param string $value + */ + private function __construct(string $value) + { + $this->value = $value; + } + + /** + * Creates an instance from a value. + * + * @param string $value + * @return static + * @throws \InvalidArgumentException + */ + public static function from(string $value): self + { + $values = static::values(); + if (!in_array($value, $values, true)) { + throw new \InvalidArgumentException( + sprintf('Invalid enum value: %s. Valid values: %s', $value, implode(', ', $values)) + ); + } + + $key = static::class . '::' . $value; + if (!isset(self::$instances[$key])) { + /** @phpstan-ignore new.static (Intentional: private constructor prevents subclass override) */ + self::$instances[$key] = new static($value); + } + + return self::$instances[$key]; + } + + /** + * Creates an instance from a value, or null if invalid. + * + * @param string $value + * @return static|null + */ + public static function tryFrom(string $value): ?self + { + try { + return static::from($value); + } catch (\InvalidArgumentException $e) { + return null; + } + } + + /** + * Returns all valid values for this enum. + * + * @return string[] + */ + abstract public static function values(): array; + + /** + * Returns all cases as instances. + * + * @return static[] + */ + public static function cases(): array + { + return array_map(fn(string $value) => static::from($value), static::values()); + } + + /** + * Gets the enum value. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Converts to string. + * + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * Compares with another instance. + * + * @param self $other + * @return bool + */ + public function equals(self $other): bool + { + return $this->value === $other->value && static::class === get_class($other); + } +} diff --git a/src/Common/Content/AudioContent.php b/src/Common/Content/AudioContent.php new file mode 100644 index 0000000..82941b8 --- /dev/null +++ b/src/Common/Content/AudioContent.php @@ -0,0 +1,187 @@ +|null + */ + protected ?array $_meta; + + /** + * @param string $data @since 2025-03-26 + * @param string $mimeType @since 2025-03-26 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2025-03-26 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + string $data, + string $mimeType, + ?Annotations $annotations = null, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->data = $data; + $this->mimeType = $mimeType; + $this->annotations = $annotations; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'audio', + * data: string, + * mimeType: string, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['data', 'mimeType']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + return new self( + self::asString($data['data']), + self::asString($data['mimeType']), + $annotations, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['data'] = $this->data; + $result['mimeType'] = $this->mimeType; + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'audio' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getData(): string + { + return $this->data; + } + + /** + * @return string + */ + public function getMimeType(): string + { + return $this->mimeType; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/Content/ImageContent.php b/src/Common/Content/ImageContent.php new file mode 100644 index 0000000..d1f3c7e --- /dev/null +++ b/src/Common/Content/ImageContent.php @@ -0,0 +1,187 @@ +|null + */ + protected ?array $_meta; + + /** + * @param string $data @since 2024-11-05 + * @param string $mimeType @since 2024-11-05 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + string $data, + string $mimeType, + ?Annotations $annotations = null, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->data = $data; + $this->mimeType = $mimeType; + $this->annotations = $annotations; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'image', + * data: string, + * mimeType: string, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['data', 'mimeType']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + return new self( + self::asString($data['data']), + self::asString($data['mimeType']), + $annotations, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['data'] = $this->data; + $result['mimeType'] = $this->mimeType; + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'image' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getData(): string + { + return $this->data; + } + + /** + * @return string + */ + public function getMimeType(): string + { + return $this->mimeType; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/Content/TextContent.php b/src/Common/Content/TextContent.php new file mode 100644 index 0000000..10ba8a2 --- /dev/null +++ b/src/Common/Content/TextContent.php @@ -0,0 +1,164 @@ +|null + */ + protected ?array $_meta; + + /** + * @param string $text @since 2024-11-05 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + string $text, + ?Annotations $annotations = null, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->text = $text; + $this->annotations = $annotations; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'text', + * text: string, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['text']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + return new self( + self::asString($data['text']), + $annotations, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['text'] = $this->text; + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'text' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getText(): string + { + return $this->text; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/Contracts/BaseMetadataInterface.php b/src/Common/Contracts/BaseMetadataInterface.php new file mode 100644 index 0000000..7891dcc --- /dev/null +++ b/src/Common/Contracts/BaseMetadataInterface.php @@ -0,0 +1,17 @@ + The array representation. + */ + public function toArray(): array; + + /** + * Creates an instance from array data. + * + * @param array $data The array data. + * @return static The created instance. + */ + public static function fromArray(array $data); +} diff --git a/src/Common/Contracts/WithJsonSchemaInterface.php b/src/Common/Contracts/WithJsonSchemaInterface.php new file mode 100644 index 0000000..3980b1c --- /dev/null +++ b/src/Common/Contracts/WithJsonSchemaInterface.php @@ -0,0 +1,20 @@ + The JSON Schema definition. + */ + public static function getJsonSchema(): array; +} diff --git a/src/Common/Core/Icon.php b/src/Common/Core/Icon.php new file mode 100644 index 0000000..e46dd82 --- /dev/null +++ b/src/Common/Core/Icon.php @@ -0,0 +1,175 @@ +|null + */ + protected ?array $sizes; + + /** + * Optional specifier for the theme this icon is designed for. `light` indicates + * the icon is designed to be used with a light background, and `dark` indicates + * the icon is designed to be used with a dark background. + * + * If not provided, the client should assume the icon can be used with any theme. + * + * @since 2025-11-25 + * + * @var 'light'|'dark'|null + */ + protected ?string $theme; + + /** + * @param string $src @since 2025-11-25 + * @param string|null $mimeType @since 2025-11-25 + * @param array|null $sizes @since 2025-11-25 + * @param 'light'|'dark'|null $theme @since 2025-11-25 + */ + public function __construct( + string $src, + ?string $mimeType = null, + ?array $sizes = null, + ?string $theme = null + ) { + $this->src = $src; + $this->mimeType = $mimeType; + $this->sizes = $sizes; + $this->theme = $theme; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * src: string, + * mimeType?: string|null, + * sizes?: array|null, + * theme?: 'light'|'dark'|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['src']); + + /** @var 'light'|'dark'|null $theme */ + $theme = isset($data['theme']) + ? self::asStringOrNull($data['theme']) + : null; + + return new self( + self::asString($data['src']), + self::asStringOrNull($data['mimeType'] ?? null), + self::asStringArrayOrNull($data['sizes'] ?? null), + $theme + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['src'] = $this->src; + if ($this->mimeType !== null) { + $result['mimeType'] = $this->mimeType; + } + if ($this->sizes !== null) { + $result['sizes'] = $this->sizes; + } + if ($this->theme !== null) { + $result['theme'] = $this->theme; + } + + return $result; + } + + /** + * @return string + */ + public function getSrc(): string + { + return $this->src; + } + + /** + * @return string|null + */ + public function getMimeType(): ?string + { + return $this->mimeType; + } + + /** + * @return array|null + */ + public function getSizes(): ?array + { + return $this->sizes; + } + + /** + * @return 'light'|'dark'|null + */ + public function getTheme(): ?string + { + return $this->theme; + } +} diff --git a/src/Common/JsonRpc/Error.php b/src/Common/JsonRpc/Error.php new file mode 100644 index 0000000..ba4c153 --- /dev/null +++ b/src/Common/JsonRpc/Error.php @@ -0,0 +1,126 @@ +code = $code; + $this->message = $message; + $this->data = $data; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * code: float, + * message: string, + * data?: mixed|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['code', 'message']); + + return new self( + self::asFloat($data['code']), + self::asString($data['message']), + $data['data'] ?? null + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['code'] = $this->code; + $result['message'] = $this->message; + if ($this->data !== null) { + $result['data'] = $this->data; + } + + return $result; + } + + /** + * @return float + */ + public function getCode(): float + { + return $this->code; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @return mixed|null + */ + public function getData() + { + return $this->data; + } +} diff --git a/src/Common/JsonRpc/JSONRPCErrorResponse.php b/src/Common/JsonRpc/JSONRPCErrorResponse.php new file mode 100644 index 0000000..8796be0 --- /dev/null +++ b/src/Common/JsonRpc/JSONRPCErrorResponse.php @@ -0,0 +1,136 @@ +jsonrpc = $jsonrpc; + $this->error = $error; + $this->id = $id; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id?: string|number|null, + * error: array|\WP\McpSchema\Common\JsonRpc\Error + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'error']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\Error $error */ + $error = is_array($data['error']) + ? Error::fromArray(self::asArray($data['error'])) + : $data['error']; + + /** @var string|number|null $id */ + $id = isset($data['id']) + ? self::asStringOrNumberOrNull($data['id']) + : null; + + return new self( + $jsonrpc, + $error, + $id + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['jsonrpc'] = $this->jsonrpc; + if ($this->id !== null) { + $result['id'] = $this->id; + } + $result['error'] = $this->error->toArray(); + + return $result; + } + + /** + * @return '2.0' + */ + public function getJsonrpc(): string + { + return $this->jsonrpc; + } + + /** + * @return string|number|null + */ + public function getId() + { + return $this->id; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\Error + */ + public function getError(): Error + { + return $this->error; + } +} diff --git a/src/Common/JsonRpc/JSONRPCNotification.php b/src/Common/JsonRpc/JSONRPCNotification.php new file mode 100644 index 0000000..1aa41b0 --- /dev/null +++ b/src/Common/JsonRpc/JSONRPCNotification.php @@ -0,0 +1,90 @@ +|null $params @since 2024-11-05 + */ + public function __construct( + string $method, + string $jsonrpc, + ?array $params = null + ) { + parent::__construct($method, $params); + $this->jsonrpc = $jsonrpc; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * method: string, + * params?: array|null, + * jsonrpc: '2.0' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['method', 'jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + return new self( + self::asString($data['method']), + $jsonrpc, + self::asArrayOrNull($data['params'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['jsonrpc'] = $this->jsonrpc; + + return $result; + } + + /** + * @return '2.0' + */ + public function getJsonrpc(): string + { + return $this->jsonrpc; + } +} diff --git a/src/Common/JsonRpc/JSONRPCRequest.php b/src/Common/JsonRpc/JSONRPCRequest.php new file mode 100644 index 0000000..6842e8c --- /dev/null +++ b/src/Common/JsonRpc/JSONRPCRequest.php @@ -0,0 +1,114 @@ +|null $params @since 2024-11-05 + */ + public function __construct( + string $method, + string $jsonrpc, + $id, + ?array $params = null + ) { + parent::__construct($method, $params); + $this->jsonrpc = $jsonrpc; + $this->id = $id; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * method: string, + * params?: array|null, + * jsonrpc: '2.0', + * id: string|number + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['method', 'jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + return new self( + self::asString($data['method']), + $jsonrpc, + $id, + self::asArrayOrNull($data['params'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['jsonrpc'] = $this->jsonrpc; + $result['id'] = $this->id; + + return $result; + } + + /** + * @return '2.0' + */ + public function getJsonrpc(): string + { + return $this->jsonrpc; + } + + /** + * @return string|number + */ + public function getId() + { + return $this->id; + } +} diff --git a/src/Common/JsonRpc/JSONRPCResultResponse.php b/src/Common/JsonRpc/JSONRPCResultResponse.php new file mode 100644 index 0000000..48ca946 --- /dev/null +++ b/src/Common/JsonRpc/JSONRPCResultResponse.php @@ -0,0 +1,133 @@ +jsonrpc = $jsonrpc; + $this->id = $id; + $this->result = $result; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * result: array|\WP\McpSchema\Common\Protocol\Result + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'result']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\Result $result */ + $result = is_array($data['result']) + ? Result::fromArray(self::asArray($data['result'])) + : $data['result']; + + return new self( + $jsonrpc, + $id, + $result + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['jsonrpc'] = $this->jsonrpc; + $result['id'] = $this->id; + $result['result'] = $this->result->toArray(); + + return $result; + } + + /** + * @return '2.0' + */ + public function getJsonrpc(): string + { + return $this->jsonrpc; + } + + /** + * @return string|number + */ + public function getId() + { + return $this->id; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Result + */ + public function getResult(): Result + { + return $this->result; + } +} diff --git a/src/Common/JsonRpc/Notification.php b/src/Common/JsonRpc/Notification.php new file mode 100644 index 0000000..b81ab76 --- /dev/null +++ b/src/Common/JsonRpc/Notification.php @@ -0,0 +1,99 @@ +|null + */ + protected ?array $params; + + /** + * @param string $method @since 2024-11-05 + * @param array|null $params @since 2024-11-05 + */ + public function __construct( + string $method, + ?array $params = null + ) { + $this->method = $method; + $this->params = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * method: string, + * params?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['method']); + + return new self( + self::asString($data['method']), + self::asArrayOrNull($data['params'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['method'] = $this->method; + if ($this->params !== null) { + $result['params'] = $this->params; + } + + return $result; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return array|null + */ + public function getParams(): ?array + { + return $this->params; + } +} diff --git a/src/Common/JsonRpc/NotificationParams.php b/src/Common/JsonRpc/NotificationParams.php new file mode 100644 index 0000000..4b64405 --- /dev/null +++ b/src/Common/JsonRpc/NotificationParams.php @@ -0,0 +1,78 @@ +|null + */ + protected ?array $_meta; + + /** + * @param array|null $_meta @since 2025-11-25 + */ + public function __construct( + ?array $_meta = null + ) { + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/JsonRpc/Request.php b/src/Common/JsonRpc/Request.php new file mode 100644 index 0000000..904410d --- /dev/null +++ b/src/Common/JsonRpc/Request.php @@ -0,0 +1,99 @@ +|null + */ + protected ?array $params; + + /** + * @param string $method @since 2024-11-05 + * @param array|null $params @since 2024-11-05 + */ + public function __construct( + string $method, + ?array $params = null + ) { + $this->method = $method; + $this->params = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * method: string, + * params?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['method']); + + return new self( + self::asString($data['method']), + self::asArrayOrNull($data['params'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['method'] = $this->method; + if ($this->params !== null) { + $result['params'] = $this->params; + } + + return $result; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return array|null + */ + public function getParams(): ?array + { + return $this->params; + } +} diff --git a/src/Common/JsonRpc/RequestParams.php b/src/Common/JsonRpc/RequestParams.php new file mode 100644 index 0000000..e4db59e --- /dev/null +++ b/src/Common/JsonRpc/RequestParams.php @@ -0,0 +1,87 @@ +_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null + */ + public function get_meta(): ?RequestParamsMeta + { + return $this->_meta; + } +} diff --git a/src/Common/JsonRpc/RequestParamsMeta.php b/src/Common/JsonRpc/RequestParamsMeta.php new file mode 100644 index 0000000..80a5fb4 --- /dev/null +++ b/src/Common/JsonRpc/RequestParamsMeta.php @@ -0,0 +1,81 @@ +progressToken = $progressToken; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * progressToken?: string|number|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var string|number|null $progressToken */ + $progressToken = isset($data['progressToken']) + ? self::asStringOrNumberOrNull($data['progressToken']) + : null; + + return new self( + $progressToken + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->progressToken !== null) { + $result['progressToken'] = $this->progressToken; + } + + return $result; + } + + /** + * @return string|number|null + */ + public function getProgressToken() + { + return $this->progressToken; + } +} diff --git a/src/Common/JsonRpc/Union/JSONRPCMessageInterface.php b/src/Common/JsonRpc/Union/JSONRPCMessageInterface.php new file mode 100644 index 0000000..b0fec67 --- /dev/null +++ b/src/Common/JsonRpc/Union/JSONRPCMessageInterface.php @@ -0,0 +1,27 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/JsonRpc/Union/JSONRPCResponseInterface.php b/src/Common/JsonRpc/Union/JSONRPCResponseInterface.php new file mode 100644 index 0000000..5093242 --- /dev/null +++ b/src/Common/JsonRpc/Union/JSONRPCResponseInterface.php @@ -0,0 +1,22 @@ +|null + */ + protected ?array $icons; + + /** + * @param string $name @since 2024-11-05 + * @param string $version @since 2024-11-05 + * @param string|null $title @since 2025-06-18 + * @param string|null $description @since 2025-11-25 + * @param string|null $websiteUrl @since 2025-11-25 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $name, + string $version, + ?string $title = null, + ?string $description = null, + ?string $websiteUrl = null, + ?array $icons = null + ) { + parent::__construct($name, $title); + $this->version = $version; + $this->description = $description; + $this->websiteUrl = $websiteUrl; + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * version: string, + * description?: string|null, + * websiteUrl?: string|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name', 'version']); + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['name']), + self::asString($data['version']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['websiteUrl'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['version'] = $this->version; + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->websiteUrl !== null) { + $result['websiteUrl'] = $this->websiteUrl; + } + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return string + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return string|null + */ + public function getWebsiteUrl(): ?string + { + return $this->websiteUrl; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Common/McpConstants.php b/src/Common/McpConstants.php new file mode 100644 index 0000000..4428418 --- /dev/null +++ b/src/Common/McpConstants.php @@ -0,0 +1,103 @@ + + */ + public static function getErrorCodeNames(): array + { + return [ + 'PARSE_ERROR' => self::PARSE_ERROR, + 'INVALID_REQUEST' => self::INVALID_REQUEST, + 'METHOD_NOT_FOUND' => self::METHOD_NOT_FOUND, + 'INVALID_PARAMS' => self::INVALID_PARAMS, + 'INTERNAL_ERROR' => self::INTERNAL_ERROR, + 'URL_ELICITATION_REQUIRED' => self::URL_ELICITATION_REQUIRED, + ]; + } + + /** + * Checks if the given error code is valid. + * + * @param int $code + * @return bool + */ + public static function isValidErrorCode(int $code): bool + { + return in_array($code, self::getErrorCodes(), true); + } + + /** + * Gets the constant name for an error code. + * + * @param int $code + * @return string|null The constant name, or null if not found + */ + public static function getErrorCodeName(int $code): ?string + { + $flipped = array_flip(self::getErrorCodeNames()); + return $flipped[$code] ?? null; + } + + /** + * Checks if an error code is a standard JSON-RPC error. + * + * Standard JSON-RPC errors are in the range -32700 to -32600. + * + * @param int $code + * @return bool + */ + public static function isStandardJsonRpcError(int $code): bool + { + return $code >= -32700 && $code <= -32600; + } +} diff --git a/src/Common/Protocol/Annotations.php b/src/Common/Protocol/Annotations.php new file mode 100644 index 0000000..1898994 --- /dev/null +++ b/src/Common/Protocol/Annotations.php @@ -0,0 +1,147 @@ +|null + */ + protected ?array $audience; + + /** + * Describes how important this data is for operating the server. + * + * A value of 1 means "most important," and indicates that the data is + * effectively required, while 0 means "least important," and indicates that + * the data is entirely optional. + * + * @since 2025-03-26 + * + * @var float|null + */ + protected ?float $priority; + + /** + * The moment the resource was last modified, as an ISO 8601 formatted string. + * + * Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + * + * Examples: last activity timestamp in an open file, timestamp when the resource + * was attached, etc. + * + * @since 2025-06-18 + * + * @var string|null + */ + protected ?string $lastModified; + + /** + * @param array<'user'|'assistant'>|null $audience @since 2025-03-26 + * @param float|null $priority @since 2025-03-26 + * @param string|null $lastModified @since 2025-06-18 + */ + public function __construct( + ?array $audience = null, + ?float $priority = null, + ?string $lastModified = null + ) { + $this->audience = $audience; + $this->priority = $priority; + $this->lastModified = $lastModified; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * audience?: array<'user'|'assistant'>|null, + * priority?: float|null, + * lastModified?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var array<'user'|'assistant'>|null $audience */ + $audience = isset($data['audience']) + ? self::asStringArrayOrNull($data['audience']) + : null; + + return new self( + $audience, + self::asFloatOrNull($data['priority'] ?? null), + self::asStringOrNull($data['lastModified'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->audience !== null) { + $result['audience'] = $this->audience; + } + if ($this->priority !== null) { + $result['priority'] = $this->priority; + } + if ($this->lastModified !== null) { + $result['lastModified'] = $this->lastModified; + } + + return $result; + } + + /** + * @return array<'user'|'assistant'>|null + */ + public function getAudience(): ?array + { + return $this->audience; + } + + /** + * @return float|null + */ + public function getPriority(): ?float + { + return $this->priority; + } + + /** + * @return string|null + */ + public function getLastModified(): ?string + { + return $this->lastModified; + } +} diff --git a/src/Common/Protocol/BaseMetadata.php b/src/Common/Protocol/BaseMetadata.php new file mode 100644 index 0000000..041e0eb --- /dev/null +++ b/src/Common/Protocol/BaseMetadata.php @@ -0,0 +1,110 @@ +name = $name; + $this->title = $title; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + return new self( + self::asString($data['name']), + self::asStringOrNull($data['title'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['name'] = $this->name; + if ($this->title !== null) { + $result['title'] = $this->title; + } + + return $result; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } +} diff --git a/src/Common/Protocol/BlobResourceContents.php b/src/Common/Protocol/BlobResourceContents.php new file mode 100644 index 0000000..fe2a070 --- /dev/null +++ b/src/Common/Protocol/BlobResourceContents.php @@ -0,0 +1,92 @@ +|null $_meta @since 2025-06-18 + */ + public function __construct( + string $uri, + string $blob, + ?string $mimeType = null, + ?array $_meta = null + ) { + parent::__construct($uri, $mimeType, $_meta); + $this->blob = $blob; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * uri: string, + * mimeType?: string|null, + * _meta?: array|null, + * blob: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri', 'blob']); + + return new self( + self::asString($data['uri']), + self::asString($data['blob']), + self::asStringOrNull($data['mimeType'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['blob'] = $this->blob; + + return $result; + } + + /** + * @return string + */ + public function getBlob(): string + { + return $this->blob; + } +} diff --git a/src/Common/Protocol/CancelledNotification.php b/src/Common/Protocol/CancelledNotification.php new file mode 100644 index 0000000..875bc98 --- /dev/null +++ b/src/Common/Protocol/CancelledNotification.php @@ -0,0 +1,106 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/cancelled', + * params: array|\WP\McpSchema\Common\Protocol\CancelledNotificationParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\Protocol\CancelledNotificationParams $params */ + $params = is_array($data['params']) + ? CancelledNotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Protocol\CancelledNotificationParams + */ + public function getTypedParams(): CancelledNotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/CancelledNotificationParams.php b/src/Common/Protocol/CancelledNotificationParams.php new file mode 100644 index 0000000..4ded430 --- /dev/null +++ b/src/Common/Protocol/CancelledNotificationParams.php @@ -0,0 +1,119 @@ +|null $_meta @since 2025-11-25 + * @param string|number|null $requestId @since 2025-11-25 + * @param string|null $reason @since 2025-11-25 + */ + public function __construct( + ?array $_meta = null, + $requestId = null, + ?string $reason = null + ) { + parent::__construct($_meta); + $this->requestId = $requestId; + $this->reason = $reason; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * requestId?: string|number|null, + * reason?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var string|number|null $requestId */ + $requestId = isset($data['requestId']) + ? self::asStringOrNumberOrNull($data['requestId']) + : null; + + return new self( + self::asArrayOrNull($data['_meta'] ?? null), + $requestId, + self::asStringOrNull($data['reason'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->requestId !== null) { + $result['requestId'] = $this->requestId; + } + if ($this->reason !== null) { + $result['reason'] = $this->reason; + } + + return $result; + } + + /** + * @return string|number|null + */ + public function getRequestId() + { + return $this->requestId; + } + + /** + * @return string|null + */ + public function getReason(): ?string + { + return $this->reason; + } +} diff --git a/src/Common/Protocol/EmbeddedResource.php b/src/Common/Protocol/EmbeddedResource.php new file mode 100644 index 0000000..b9d2ab3 --- /dev/null +++ b/src/Common/Protocol/EmbeddedResource.php @@ -0,0 +1,166 @@ +|null + */ + protected ?array $_meta; + + /** + * @param \WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents $resource @since 2024-11-05 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + $resource, + ?Annotations $annotations = null, + ?array $_meta = null + ) { + $this->type = self::TYPE; + $this->resource = $resource; + $this->annotations = $annotations; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'resource', + * resource: \WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['resource']); + + /** @var \WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents $resource */ + $resource = $data['resource']; + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + return new self( + $resource, + $annotations, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['resource'] = (is_object($this->resource) && method_exists($this->resource, 'toArray')) ? $this->resource->toArray() : $this->resource; + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return 'resource' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return \WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/Protocol/EmptyResult.php b/src/Common/Protocol/EmptyResult.php new file mode 100644 index 0000000..0c10ebd --- /dev/null +++ b/src/Common/Protocol/EmptyResult.php @@ -0,0 +1,29 @@ +> + */ + public const REGISTRY = [ + 'notifications/cancelled' => CancelledNotification::class, + 'notifications/progress' => ProgressNotification::class, + 'notifications/initialized' => InitializedNotification::class, + 'notifications/roots/list_changed' => RootsListChangedNotification::class, + 'notifications/tasks/status' => TaskStatusNotification::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ClientNotificationInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ClientNotificationInterface + { + if (!isset($data['method'])) { + throw new \InvalidArgumentException('Missing discriminator field: method'); + } + + /** @var string $method */ + $method = $data['method']; + if (!isset(self::REGISTRY[$method])) { + throw new \InvalidArgumentException(sprintf( + "Unknown method value '%s'. Valid values: %s", + $method, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$method]; + return $class::fromArray($data); + } + + /** + * Checks if a method value is supported by this factory. + * + * @param string $method + * @return bool + */ + public static function supports(string $method): bool + { + return isset(self::REGISTRY[$method]); + } + + /** + * Returns all supported method values. + * + * @return array + */ + public static function methods(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Common/Protocol/Factory/ClientRequestFactory.php b/src/Common/Protocol/Factory/ClientRequestFactory.php new file mode 100644 index 0000000..7d014d3 --- /dev/null +++ b/src/Common/Protocol/Factory/ClientRequestFactory.php @@ -0,0 +1,107 @@ +> + */ + public const REGISTRY = [ + 'ping' => PingRequest::class, + 'initialize' => InitializeRequest::class, + 'completion/complete' => CompleteRequest::class, + 'logging/setLevel' => SetLevelRequest::class, + 'prompts/get' => GetPromptRequest::class, + 'prompts/list' => ListPromptsRequest::class, + 'resources/list' => ListResourcesRequest::class, + 'resources/templates/list' => ListResourceTemplatesRequest::class, + 'resources/read' => ReadResourceRequest::class, + 'resources/subscribe' => SubscribeRequest::class, + 'resources/unsubscribe' => UnsubscribeRequest::class, + 'tools/call' => CallToolRequest::class, + 'tools/list' => ListToolsRequest::class, + 'tasks/get' => GetTaskRequest::class, + 'tasks/result' => GetTaskPayloadRequest::class, + 'tasks/list' => ListTasksRequest::class, + 'tasks/cancel' => CancelTaskRequest::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ClientRequestInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ClientRequestInterface + { + if (!isset($data['method'])) { + throw new \InvalidArgumentException('Missing discriminator field: method'); + } + + /** @var string $method */ + $method = $data['method']; + if (!isset(self::REGISTRY[$method])) { + throw new \InvalidArgumentException(sprintf( + "Unknown method value '%s'. Valid values: %s", + $method, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$method]; + return $class::fromArray($data); + } + + /** + * Checks if a method value is supported by this factory. + * + * @param string $method + * @return bool + */ + public static function supports(string $method): bool + { + return isset(self::REGISTRY[$method]); + } + + /** + * Returns all supported method values. + * + * @return array + */ + public static function methods(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Common/Protocol/Factory/ContentBlockFactory.php b/src/Common/Protocol/Factory/ContentBlockFactory.php new file mode 100644 index 0000000..71fd25c --- /dev/null +++ b/src/Common/Protocol/Factory/ContentBlockFactory.php @@ -0,0 +1,83 @@ +> + */ + public const REGISTRY = [ + 'text' => TextContent::class, + 'image' => ImageContent::class, + 'audio' => AudioContent::class, + 'resource_link' => ResourceLink::class, + 'resource' => EmbeddedResource::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ContentBlockInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ContentBlockInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + /** @var string $type */ + $type = $data['type']; + if (!isset(self::REGISTRY[$type])) { + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + $type, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$type]; + return $class::fromArray($data); + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Common/Protocol/Factory/SamplingMessageContentBlockFactory.php b/src/Common/Protocol/Factory/SamplingMessageContentBlockFactory.php new file mode 100644 index 0000000..5292497 --- /dev/null +++ b/src/Common/Protocol/Factory/SamplingMessageContentBlockFactory.php @@ -0,0 +1,83 @@ +> + */ + public const REGISTRY = [ + 'text' => TextContent::class, + 'image' => ImageContent::class, + 'audio' => AudioContent::class, + 'tool_use' => ToolUseContent::class, + 'tool_result' => ToolResultContent::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return SamplingMessageContentBlockInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): SamplingMessageContentBlockInterface + { + if (!isset($data['type'])) { + throw new \InvalidArgumentException('Missing discriminator field: type'); + } + + /** @var string $type */ + $type = $data['type']; + if (!isset(self::REGISTRY[$type])) { + throw new \InvalidArgumentException(sprintf( + "Unknown type value '%s'. Valid values: %s", + $type, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$type]; + return $class::fromArray($data); + } + + /** + * Checks if a type value is supported by this factory. + * + * @param string $type + * @return bool + */ + public static function supports(string $type): bool + { + return isset(self::REGISTRY[$type]); + } + + /** + * Returns all supported type values. + * + * @return array + */ + public static function types(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Common/Protocol/Factory/ServerRequestFactory.php b/src/Common/Protocol/Factory/ServerRequestFactory.php new file mode 100644 index 0000000..c494894 --- /dev/null +++ b/src/Common/Protocol/Factory/ServerRequestFactory.php @@ -0,0 +1,89 @@ +> + */ + public const REGISTRY = [ + 'ping' => PingRequest::class, + 'sampling/createMessage' => CreateMessageRequest::class, + 'roots/list' => ListRootsRequest::class, + 'elicitation/create' => ElicitRequest::class, + 'tasks/get' => GetTaskRequest::class, + 'tasks/result' => GetTaskPayloadRequest::class, + 'tasks/list' => ListTasksRequest::class, + 'tasks/cancel' => CancelTaskRequest::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ServerRequestInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ServerRequestInterface + { + if (!isset($data['method'])) { + throw new \InvalidArgumentException('Missing discriminator field: method'); + } + + /** @var string $method */ + $method = $data['method']; + if (!isset(self::REGISTRY[$method])) { + throw new \InvalidArgumentException(sprintf( + "Unknown method value '%s'. Valid values: %s", + $method, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$method]; + return $class::fromArray($data); + } + + /** + * Checks if a method value is supported by this factory. + * + * @param string $method + * @return bool + */ + public static function supports(string $method): bool + { + return isset(self::REGISTRY[$method]); + } + + /** + * Returns all supported method values. + * + * @return array + */ + public static function methods(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Common/Protocol/GetTaskPayloadRequest.php b/src/Common/Protocol/GetTaskPayloadRequest.php new file mode 100644 index 0000000..087f099 --- /dev/null +++ b/src/Common/Protocol/GetTaskPayloadRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'tasks/result', + * params: array|\WP\McpSchema\Common\Protocol\GetTaskPayloadRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\GetTaskPayloadRequestParams $params */ + $params = is_array($data['params']) + ? GetTaskPayloadRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Protocol\GetTaskPayloadRequestParams + */ + public function getTypedParams(): GetTaskPayloadRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/GetTaskPayloadRequestParams.php b/src/Common/Protocol/GetTaskPayloadRequestParams.php new file mode 100644 index 0000000..0cbf3d3 --- /dev/null +++ b/src/Common/Protocol/GetTaskPayloadRequestParams.php @@ -0,0 +1,74 @@ +taskId = $taskId; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskId: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId']); + + return new self( + self::asString($data['taskId']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['taskId'] = $this->taskId; + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } +} diff --git a/src/Common/Protocol/GetTaskPayloadResult.php b/src/Common/Protocol/GetTaskPayloadResult.php new file mode 100644 index 0000000..9d0238b --- /dev/null +++ b/src/Common/Protocol/GetTaskPayloadResult.php @@ -0,0 +1,66 @@ +|null $_meta @since 2025-11-25 + */ + public function __construct( + ?array $_meta = null + ) { + parent::__construct($_meta); + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Common/Protocol/Icons.php b/src/Common/Protocol/Icons.php new file mode 100644 index 0000000..2f6475c --- /dev/null +++ b/src/Common/Protocol/Icons.php @@ -0,0 +1,99 @@ +|null + */ + protected ?array $icons; + + /** + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + ?array $icons = null + ) { + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Common/Protocol/InitializeRequest.php b/src/Common/Protocol/InitializeRequest.php new file mode 100644 index 0000000..9d6de4e --- /dev/null +++ b/src/Common/Protocol/InitializeRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'initialize', + * params: array|\WP\McpSchema\Common\Protocol\InitializeRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\InitializeRequestParams $params */ + $params = is_array($data['params']) + ? InitializeRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Protocol\InitializeRequestParams + */ + public function getTypedParams(): InitializeRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/InitializeRequestParams.php b/src/Common/Protocol/InitializeRequestParams.php new file mode 100644 index 0000000..72566d0 --- /dev/null +++ b/src/Common/Protocol/InitializeRequestParams.php @@ -0,0 +1,147 @@ +protocolVersion = $protocolVersion; + $this->capabilities = $capabilities; + $this->clientInfo = $clientInfo; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * protocolVersion: string, + * capabilities: array|\WP\McpSchema\Client\Lifecycle\ClientCapabilities, + * clientInfo: array|\WP\McpSchema\Common\Lifecycle\Implementation + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['protocolVersion', 'capabilities', 'clientInfo']); + + /** @var \WP\McpSchema\Client\Lifecycle\ClientCapabilities $capabilities */ + $capabilities = is_array($data['capabilities']) + ? ClientCapabilities::fromArray(self::asArray($data['capabilities'])) + : $data['capabilities']; + + /** @var \WP\McpSchema\Common\Lifecycle\Implementation $clientInfo */ + $clientInfo = is_array($data['clientInfo']) + ? Implementation::fromArray(self::asArray($data['clientInfo'])) + : $data['clientInfo']; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['protocolVersion']), + $capabilities, + $clientInfo, + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['protocolVersion'] = $this->protocolVersion; + $result['capabilities'] = $this->capabilities->toArray(); + $result['clientInfo'] = $this->clientInfo->toArray(); + + return $result; + } + + /** + * @return string + */ + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + /** + * @return \WP\McpSchema\Client\Lifecycle\ClientCapabilities + */ + public function getCapabilities(): ClientCapabilities + { + return $this->capabilities; + } + + /** + * @return \WP\McpSchema\Common\Lifecycle\Implementation + */ + public function getClientInfo(): Implementation + { + return $this->clientInfo; + } +} diff --git a/src/Common/Protocol/InitializeResult.php b/src/Common/Protocol/InitializeResult.php new file mode 100644 index 0000000..5e0c103 --- /dev/null +++ b/src/Common/Protocol/InitializeResult.php @@ -0,0 +1,167 @@ +|null $_meta @since 2024-11-05 + * @param string|null $instructions @since 2024-11-05 + */ + public function __construct( + string $protocolVersion, + ServerCapabilities $capabilities, + Implementation $serverInfo, + ?array $_meta = null, + ?string $instructions = null + ) { + parent::__construct($_meta); + $this->protocolVersion = $protocolVersion; + $this->capabilities = $capabilities; + $this->serverInfo = $serverInfo; + $this->instructions = $instructions; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * protocolVersion: string, + * capabilities: array|\WP\McpSchema\Server\Lifecycle\ServerCapabilities, + * serverInfo: array|\WP\McpSchema\Common\Lifecycle\Implementation, + * instructions?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['protocolVersion', 'capabilities', 'serverInfo']); + + /** @var \WP\McpSchema\Server\Lifecycle\ServerCapabilities $capabilities */ + $capabilities = is_array($data['capabilities']) + ? ServerCapabilities::fromArray(self::asArray($data['capabilities'])) + : $data['capabilities']; + + /** @var \WP\McpSchema\Common\Lifecycle\Implementation $serverInfo */ + $serverInfo = is_array($data['serverInfo']) + ? Implementation::fromArray(self::asArray($data['serverInfo'])) + : $data['serverInfo']; + + return new self( + self::asString($data['protocolVersion']), + $capabilities, + $serverInfo, + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['instructions'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['protocolVersion'] = $this->protocolVersion; + $result['capabilities'] = $this->capabilities->toArray(); + $result['serverInfo'] = $this->serverInfo->toArray(); + if ($this->instructions !== null) { + $result['instructions'] = $this->instructions; + } + + return $result; + } + + /** + * @return string + */ + public function getProtocolVersion(): string + { + return $this->protocolVersion; + } + + /** + * @return \WP\McpSchema\Server\Lifecycle\ServerCapabilities + */ + public function getCapabilities(): ServerCapabilities + { + return $this->capabilities; + } + + /** + * @return \WP\McpSchema\Common\Lifecycle\Implementation + */ + public function getServerInfo(): Implementation + { + return $this->serverInfo; + } + + /** + * @return string|null + */ + public function getInstructions(): ?string + { + return $this->instructions; + } +} diff --git a/src/Common/Protocol/InitializedNotification.php b/src/Common/Protocol/InitializedNotification.php new file mode 100644 index 0000000..cfc207d --- /dev/null +++ b/src/Common/Protocol/InitializedNotification.php @@ -0,0 +1,102 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/initialized', + * params?: array|\WP\McpSchema\Common\JsonRpc\NotificationParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\NotificationParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? NotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\NotificationParams|null + */ + public function getTypedParams(): ?NotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/PaginatedRequest.php b/src/Common/Protocol/PaginatedRequest.php new file mode 100644 index 0000000..a148221 --- /dev/null +++ b/src/Common/Protocol/PaginatedRequest.php @@ -0,0 +1,103 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: string, + * params?: array|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'method']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + self::asString($data['method']), + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null + */ + public function getTypedParams(): ?PaginatedRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/PaginatedRequestParams.php b/src/Common/Protocol/PaginatedRequestParams.php new file mode 100644 index 0000000..4e2803a --- /dev/null +++ b/src/Common/Protocol/PaginatedRequestParams.php @@ -0,0 +1,94 @@ +cursor = $cursor; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * cursor?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + $_meta, + self::asStringOrNull($data['cursor'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->cursor !== null) { + $result['cursor'] = $this->cursor; + } + + return $result; + } + + /** + * @return string|null + */ + public function getCursor(): ?string + { + return $this->cursor; + } +} diff --git a/src/Common/Protocol/PaginatedResult.php b/src/Common/Protocol/PaginatedResult.php new file mode 100644 index 0000000..a58bd61 --- /dev/null +++ b/src/Common/Protocol/PaginatedResult.php @@ -0,0 +1,83 @@ +|null $_meta @since 2024-11-05 + * @param string|null $nextCursor @since 2024-11-05 + */ + public function __construct( + ?array $_meta = null, + ?string $nextCursor = null + ) { + parent::__construct($_meta); + $this->nextCursor = $nextCursor; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * nextCursor?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['nextCursor'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->nextCursor !== null) { + $result['nextCursor'] = $this->nextCursor; + } + + return $result; + } + + /** + * @return string|null + */ + public function getNextCursor(): ?string + { + return $this->nextCursor; + } +} diff --git a/src/Common/Protocol/PingRequest.php b/src/Common/Protocol/PingRequest.php new file mode 100644 index 0000000..711e4ce --- /dev/null +++ b/src/Common/Protocol/PingRequest.php @@ -0,0 +1,110 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'ping', + * params?: array|\WP\McpSchema\Common\JsonRpc\RequestParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? RequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\RequestParams|null + */ + public function getTypedParams(): ?RequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/ProgressNotification.php b/src/Common/Protocol/ProgressNotification.php new file mode 100644 index 0000000..f32e8cd --- /dev/null +++ b/src/Common/Protocol/ProgressNotification.php @@ -0,0 +1,98 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/progress', + * params: array|\WP\McpSchema\Common\Protocol\ProgressNotificationParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\Protocol\ProgressNotificationParams $params */ + $params = is_array($data['params']) + ? ProgressNotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Protocol\ProgressNotificationParams + */ + public function getTypedParams(): ProgressNotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Protocol/ProgressNotificationParams.php b/src/Common/Protocol/ProgressNotificationParams.php new file mode 100644 index 0000000..a5d6d88 --- /dev/null +++ b/src/Common/Protocol/ProgressNotificationParams.php @@ -0,0 +1,161 @@ +|null $_meta @since 2025-11-25 + * @param int|null $total @since 2025-11-25 + * @param string|null $message @since 2025-11-25 + */ + public function __construct( + $progressToken, + float $progress, + ?array $_meta = null, + ?int $total = null, + ?string $message = null + ) { + parent::__construct($_meta); + $this->progressToken = $progressToken; + $this->progress = $progress; + $this->total = $total; + $this->message = $message; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * progressToken: string|number, + * progress: float, + * total?: int|null, + * message?: string|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['progressToken', 'progress']); + + /** @var string|number $progressToken */ + $progressToken = self::asStringOrNumber($data['progressToken']); + + return new self( + $progressToken, + self::asFloat($data['progress']), + self::asArrayOrNull($data['_meta'] ?? null), + self::asIntOrNull($data['total'] ?? null), + self::asStringOrNull($data['message'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['progressToken'] = $this->progressToken; + $result['progress'] = $this->progress; + if ($this->total !== null) { + $result['total'] = $this->total; + } + if ($this->message !== null) { + $result['message'] = $this->message; + } + + return $result; + } + + /** + * @return string|number + */ + public function getProgressToken() + { + return $this->progressToken; + } + + /** + * @return float + */ + public function getProgress(): float + { + return $this->progress; + } + + /** + * @return int|null + */ + public function getTotal(): ?int + { + return $this->total; + } + + /** + * @return string|null + */ + public function getMessage(): ?string + { + return $this->message; + } +} diff --git a/src/Common/Protocol/Result.php b/src/Common/Protocol/Result.php new file mode 100644 index 0000000..7196ac5 --- /dev/null +++ b/src/Common/Protocol/Result.php @@ -0,0 +1,78 @@ +|null + */ + protected ?array $_meta; + + /** + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + ?array $_meta = null + ) { + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Common/Protocol/TextResourceContents.php b/src/Common/Protocol/TextResourceContents.php new file mode 100644 index 0000000..2a5e8f5 --- /dev/null +++ b/src/Common/Protocol/TextResourceContents.php @@ -0,0 +1,92 @@ +|null $_meta @since 2025-06-18 + */ + public function __construct( + string $uri, + string $text, + ?string $mimeType = null, + ?array $_meta = null + ) { + parent::__construct($uri, $mimeType, $_meta); + $this->text = $text; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * uri: string, + * mimeType?: string|null, + * _meta?: array|null, + * text: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri', 'text']); + + return new self( + self::asString($data['uri']), + self::asString($data['text']), + self::asStringOrNull($data['mimeType'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['text'] = $this->text; + + return $result; + } + + /** + * @return string + */ + public function getText(): string + { + return $this->text; + } +} diff --git a/src/Common/Protocol/URLElicitationRequiredError.php b/src/Common/Protocol/URLElicitationRequiredError.php new file mode 100644 index 0000000..e4568c7 --- /dev/null +++ b/src/Common/Protocol/URLElicitationRequiredError.php @@ -0,0 +1,78 @@ +error = $error; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * error: mixed + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['error']); + + return new self( + $data['error'] + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['error'] = $this->error; + + return $result; + } + + /** + * @return mixed + */ + public function getError() + { + return $this->error; + } +} diff --git a/src/Common/Protocol/Union/ClientNotificationInterface.php b/src/Common/Protocol/Union/ClientNotificationInterface.php new file mode 100644 index 0000000..ce2072e --- /dev/null +++ b/src/Common/Protocol/Union/ClientNotificationInterface.php @@ -0,0 +1,27 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/Protocol/Union/ClientRequestInterface.php b/src/Common/Protocol/Union/ClientRequestInterface.php new file mode 100644 index 0000000..a2ea5ef --- /dev/null +++ b/src/Common/Protocol/Union/ClientRequestInterface.php @@ -0,0 +1,39 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/Protocol/Union/ContentBlockInterface.php b/src/Common/Protocol/Union/ContentBlockInterface.php new file mode 100644 index 0000000..3872050 --- /dev/null +++ b/src/Common/Protocol/Union/ContentBlockInterface.php @@ -0,0 +1,27 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/Protocol/Union/SamplingMessageContentBlockInterface.php b/src/Common/Protocol/Union/SamplingMessageContentBlockInterface.php new file mode 100644 index 0000000..e41a977 --- /dev/null +++ b/src/Common/Protocol/Union/SamplingMessageContentBlockInterface.php @@ -0,0 +1,27 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/Protocol/Union/ServerRequestInterface.php b/src/Common/Protocol/Union/ServerRequestInterface.php new file mode 100644 index 0000000..0b8521a --- /dev/null +++ b/src/Common/Protocol/Union/ServerRequestInterface.php @@ -0,0 +1,30 @@ + + */ + public function toArray(): array; +} diff --git a/src/Common/Tasks/CancelTaskRequest.php b/src/Common/Tasks/CancelTaskRequest.php new file mode 100644 index 0000000..127ecd0 --- /dev/null +++ b/src/Common/Tasks/CancelTaskRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'tasks/cancel', + * params: array|\WP\McpSchema\Common\Tasks\CancelTaskRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Tasks\CancelTaskRequestParams $params */ + $params = is_array($data['params']) + ? CancelTaskRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Tasks\CancelTaskRequestParams + */ + public function getTypedParams(): CancelTaskRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Tasks/CancelTaskRequestParams.php b/src/Common/Tasks/CancelTaskRequestParams.php new file mode 100644 index 0000000..0eb4bc7 --- /dev/null +++ b/src/Common/Tasks/CancelTaskRequestParams.php @@ -0,0 +1,74 @@ +taskId = $taskId; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskId: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId']); + + return new self( + self::asString($data['taskId']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['taskId'] = $this->taskId; + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } +} diff --git a/src/Common/Tasks/CancelTaskResult.php b/src/Common/Tasks/CancelTaskResult.php new file mode 100644 index 0000000..70b02bd --- /dev/null +++ b/src/Common/Tasks/CancelTaskResult.php @@ -0,0 +1,236 @@ +|null $_meta @since 2025-11-25 + * @param string|null $statusMessage @since 2025-11-25 + * @param int|null $pollInterval @since 2025-11-25 + */ + public function __construct( + string $taskId, + string $status, + string $createdAt, + string $lastUpdatedAt, + int $ttl, + ?array $_meta = null, + ?string $statusMessage = null, + ?int $pollInterval = null + ) { + parent::__construct($_meta); + $this->taskId = $taskId; + $this->status = $status; + $this->statusMessage = $statusMessage; + $this->createdAt = $createdAt; + $this->lastUpdatedAt = $lastUpdatedAt; + $this->ttl = $ttl; + $this->pollInterval = $pollInterval; + } + + /** + * Creates an instance from an array. + * + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId', 'status', 'createdAt', 'lastUpdatedAt', 'ttl']); + + /** @var 'working'|'input_required'|'completed'|'failed'|'cancelled' $status */ + $status = self::asString($data['status']); + + return new self( + self::asString($data['taskId']), + $status, + self::asString($data['createdAt']), + self::asString($data['lastUpdatedAt']), + self::asInt($data['ttl']), + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['statusMessage'] ?? null), + self::asIntOrNull($data['pollInterval'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['taskId'] = $this->taskId; + $result['status'] = $this->status; + if ($this->statusMessage !== null) { + $result['statusMessage'] = $this->statusMessage; + } + $result['createdAt'] = $this->createdAt; + $result['lastUpdatedAt'] = $this->lastUpdatedAt; + $result['ttl'] = $this->ttl; + if ($this->pollInterval !== null) { + $result['pollInterval'] = $this->pollInterval; + } + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } + + /** + * @return 'working'|'input_required'|'completed'|'failed'|'cancelled' + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return string|null + */ + public function getStatusMessage(): ?string + { + return $this->statusMessage; + } + + /** + * @return string + */ + public function getCreatedAt(): string + { + return $this->createdAt; + } + + /** + * @return string + */ + public function getLastUpdatedAt(): string + { + return $this->lastUpdatedAt; + } + + /** + * @return int|null + */ + public function getTtl(): ?int + { + return $this->ttl; + } + + /** + * @return int|null + */ + public function getPollInterval(): ?int + { + return $this->pollInterval; + } +} diff --git a/src/Common/Tasks/GetTaskRequest.php b/src/Common/Tasks/GetTaskRequest.php new file mode 100644 index 0000000..09b0fed --- /dev/null +++ b/src/Common/Tasks/GetTaskRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'tasks/get', + * params: array|\WP\McpSchema\Common\Tasks\GetTaskRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Tasks\GetTaskRequestParams $params */ + $params = is_array($data['params']) + ? GetTaskRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Common\Tasks\GetTaskRequestParams + */ + public function getTypedParams(): GetTaskRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Common/Tasks/GetTaskRequestParams.php b/src/Common/Tasks/GetTaskRequestParams.php new file mode 100644 index 0000000..67ec62c --- /dev/null +++ b/src/Common/Tasks/GetTaskRequestParams.php @@ -0,0 +1,74 @@ +taskId = $taskId; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskId: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId']); + + return new self( + self::asString($data['taskId']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['taskId'] = $this->taskId; + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } +} diff --git a/src/Common/Tasks/GetTaskResult.php b/src/Common/Tasks/GetTaskResult.php new file mode 100644 index 0000000..ee13ee9 --- /dev/null +++ b/src/Common/Tasks/GetTaskResult.php @@ -0,0 +1,236 @@ +|null $_meta @since 2025-11-25 + * @param string|null $statusMessage @since 2025-11-25 + * @param int|null $pollInterval @since 2025-11-25 + */ + public function __construct( + string $taskId, + string $status, + string $createdAt, + string $lastUpdatedAt, + int $ttl, + ?array $_meta = null, + ?string $statusMessage = null, + ?int $pollInterval = null + ) { + parent::__construct($_meta); + $this->taskId = $taskId; + $this->status = $status; + $this->statusMessage = $statusMessage; + $this->createdAt = $createdAt; + $this->lastUpdatedAt = $lastUpdatedAt; + $this->ttl = $ttl; + $this->pollInterval = $pollInterval; + } + + /** + * Creates an instance from an array. + * + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['taskId', 'status', 'createdAt', 'lastUpdatedAt', 'ttl']); + + /** @var 'working'|'input_required'|'completed'|'failed'|'cancelled' $status */ + $status = self::asString($data['status']); + + return new self( + self::asString($data['taskId']), + $status, + self::asString($data['createdAt']), + self::asString($data['lastUpdatedAt']), + self::asInt($data['ttl']), + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['statusMessage'] ?? null), + self::asIntOrNull($data['pollInterval'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['taskId'] = $this->taskId; + $result['status'] = $this->status; + if ($this->statusMessage !== null) { + $result['statusMessage'] = $this->statusMessage; + } + $result['createdAt'] = $this->createdAt; + $result['lastUpdatedAt'] = $this->lastUpdatedAt; + $result['ttl'] = $this->ttl; + if ($this->pollInterval !== null) { + $result['pollInterval'] = $this->pollInterval; + } + + return $result; + } + + /** + * @return string + */ + public function getTaskId(): string + { + return $this->taskId; + } + + /** + * @return 'working'|'input_required'|'completed'|'failed'|'cancelled' + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return string|null + */ + public function getStatusMessage(): ?string + { + return $this->statusMessage; + } + + /** + * @return string + */ + public function getCreatedAt(): string + { + return $this->createdAt; + } + + /** + * @return string + */ + public function getLastUpdatedAt(): string + { + return $this->lastUpdatedAt; + } + + /** + * @return int|null + */ + public function getTtl(): ?int + { + return $this->ttl; + } + + /** + * @return int|null + */ + public function getPollInterval(): ?int + { + return $this->pollInterval; + } +} diff --git a/src/Common/Tasks/ListTasksRequest.php b/src/Common/Tasks/ListTasksRequest.php new file mode 100644 index 0000000..50b3d13 --- /dev/null +++ b/src/Common/Tasks/ListTasksRequest.php @@ -0,0 +1,95 @@ +|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null, + * jsonrpc: '2.0', + * id: string|number, + * method: 'tasks/list' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Common/Tasks/ListTasksResult.php b/src/Common/Tasks/ListTasksResult.php new file mode 100644 index 0000000..61f8728 --- /dev/null +++ b/src/Common/Tasks/ListTasksResult.php @@ -0,0 +1,98 @@ + + */ + protected array $tasks; + + /** + * @param array<\WP\McpSchema\Client\Tasks\Task> $tasks @since 2025-11-25 + * @param string|null $nextCursor @since 2025-11-25 + * @param array|null $_meta @since 2025-11-25 + */ + public function __construct( + array $tasks, + ?string $nextCursor = null, + ?array $_meta = null + ) { + parent::__construct($_meta, $nextCursor); + $this->tasks = $tasks; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * nextCursor?: string|null, + * _meta?: array|null, + * tasks: array|\WP\McpSchema\Client\Tasks\Task> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['tasks']); + + /** @var array<\WP\McpSchema\Client\Tasks\Task> $tasks */ + $tasks = array_map( + static fn($item) => is_array($item) + ? Task::fromArray($item) + : $item, + self::asArray($data['tasks']) + ); + + return new self( + $tasks, + self::asStringOrNull($data['nextCursor'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['tasks'] = array_map(static fn($item) => $item->toArray(), $this->tasks); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Client\Tasks\Task> + */ + public function getTasks(): array + { + return $this->tasks; + } +} diff --git a/src/Common/Tasks/TaskAugmentedRequestParams.php b/src/Common/Tasks/TaskAugmentedRequestParams.php new file mode 100644 index 0000000..82953f2 --- /dev/null +++ b/src/Common/Tasks/TaskAugmentedRequestParams.php @@ -0,0 +1,106 @@ +task = $task; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * task?: array|\WP\McpSchema\Client\Tasks\TaskMetadata|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + /** @var \WP\McpSchema\Client\Tasks\TaskMetadata|null $task */ + $task = isset($data['task']) + ? (is_array($data['task']) + ? TaskMetadata::fromArray(self::asArray($data['task'])) + : $data['task']) + : null; + + return new self( + $_meta, + $task + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->task !== null) { + $result['task'] = $this->task->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Client\Tasks\TaskMetadata|null + */ + public function getTask(): ?TaskMetadata + { + return $this->task; + } +} diff --git a/src/Common/Tasks/TaskStatusNotification.php b/src/Common/Tasks/TaskStatusNotification.php new file mode 100644 index 0000000..b1af1b3 --- /dev/null +++ b/src/Common/Tasks/TaskStatusNotification.php @@ -0,0 +1,92 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/tasks/status', + * params: mixed + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + return new self( + $jsonrpc, + $data['params'] + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams; + + return $result; + } + + /** + * @return mixed + */ + public function getTypedParams() + { + return $this->typedParams; + } +} diff --git a/src/Common/Traits/ValidatesRequiredFields.php b/src/Common/Traits/ValidatesRequiredFields.php new file mode 100644 index 0000000..3069281 --- /dev/null +++ b/src/Common/Traits/ValidatesRequiredFields.php @@ -0,0 +1,379 @@ + $data The input data array + * @param string[] $requiredFields List of required field names + * @return void + * @throws \InvalidArgumentException If any required fields are missing + */ + protected static function assertRequired(array $data, array $requiredFields): void + { + $missing = array_filter( + $requiredFields, + static fn(string $field): bool => !array_key_exists($field, $data) + ); + + if (count($missing) > 0) { + throw new \InvalidArgumentException(sprintf( + '%s: missing required field(s): %s', + static::class, + implode(', ', $missing) + )); + } + } + + /** + * Asserts a value is a string and returns it. + * + * @param mixed $value + * @return string + * @phpstan-assert string $value + */ + protected static function asString($value): string + { + if (!is_string($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected string, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Asserts a value is an int and returns it. + * + * @param mixed $value + * @return int + * @phpstan-assert int $value + */ + protected static function asInt($value): int + { + if (!is_int($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected int, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Asserts a value is a float and returns it. + * + * @param mixed $value + * @return float + * @phpstan-assert float $value + */ + protected static function asFloat($value): float + { + if (!is_float($value) && !is_int($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected float, got %s', + gettype($value) + )); + } + return (float) $value; + } + + /** + * Asserts a value is a bool and returns it. + * + * @param mixed $value + * @return bool + * @phpstan-assert bool $value + */ + protected static function asBool($value): bool + { + if (!is_bool($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected bool, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Asserts a value is an array and returns it. + * + * @param mixed $value + * @return array + * @phpstan-assert array $value + */ + protected static function asArray($value): array + { + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected array, got %s', + gettype($value) + )); + } + /** @var array */ + return $value; + } + + /** + * Returns a value as string or null. + * + * @param mixed $value + * @return string|null + */ + protected static function asStringOrNull($value): ?string + { + return $value === null ? null : self::asString($value); + } + + /** + * Returns a value as int or null. + * + * @param mixed $value + * @return int|null + */ + protected static function asIntOrNull($value): ?int + { + return $value === null ? null : self::asInt($value); + } + + /** + * Returns a value as float or null. + * + * @param mixed $value + * @return float|null + */ + protected static function asFloatOrNull($value): ?float + { + return $value === null ? null : self::asFloat($value); + } + + /** + * Returns a value as bool or null. + * + * @param mixed $value + * @return bool|null + */ + protected static function asBoolOrNull($value): ?bool + { + return $value === null ? null : self::asBool($value); + } + + /** + * Returns a value as array or null. + * + * @param mixed $value + * @return array|null + */ + protected static function asArrayOrNull($value): ?array + { + return $value === null ? null : self::asArray($value); + } + + /** + * Asserts a value is an object and returns it. + * + * @param mixed $value + * @return object + * @phpstan-assert object $value + */ + protected static function asObject($value): object + { + if (!is_object($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected object, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Returns a value as object or null. + * + * @param mixed $value + * @return object|null + */ + protected static function asObjectOrNull($value): ?object + { + return $value === null ? null : self::asObject($value); + } + + /** + * Asserts a value is an array of strings and returns it. + * + * @param mixed $value + * @return array + */ + protected static function asStringArray($value): array + { + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected array, got %s', + gettype($value) + )); + } + /** @var array */ + return array_values(array_map(static fn($item): string => (string) $item, $value)); + } + + /** + * Returns a value as array of strings or null. + * + * @param mixed $value + * @return array|null + */ + protected static function asStringArrayOrNull($value): ?array + { + return $value === null ? null : self::asStringArray($value); + } + + /** + * Asserts a value is an associative array with string values only. + * + * Used for MCP types like { [key: string]: string } index signatures. + * + * @param mixed $value + * @return array + * @phpstan-assert array $value + */ + protected static function asStringMap($value): array + { + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected array, got %s', + gettype($value) + )); + } + foreach ($value as $key => $v) { + if (!is_string($v)) { + throw new \InvalidArgumentException(sprintf( + 'Expected string value for key "%s", got %s', + (string) $key, + gettype($v) + )); + } + } + /** @var array */ + return $value; + } + + /** + * Returns a value as string map or null. + * + * Used for optional MCP types like { [key: string]: string } | null. + * + * @param mixed $value + * @return array|null + */ + protected static function asStringMapOrNull($value): ?array + { + return $value === null ? null : self::asStringMap($value); + } + + /** + * Asserts a value is an associative array with object values only. + * + * Used for MCP types like { [key: string]: object } index signatures. + * + * @param mixed $value + * @return array + * @phpstan-assert array $value + */ + protected static function asObjectMap($value): array + { + if (!is_array($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected array, got %s', + gettype($value) + )); + } + foreach ($value as $key => $v) { + if (!is_object($v)) { + throw new \InvalidArgumentException(sprintf( + 'Expected object value for key "%s", got %s', + (string) $key, + gettype($v) + )); + } + } + /** @var array */ + return $value; + } + + /** + * Returns a value as object map or null. + * + * Used for optional MCP types like { [key: string]: object } | null. + * + * @param mixed $value + * @return array|null + */ + protected static function asObjectMapOrNull($value): ?array + { + return $value === null ? null : self::asObjectMap($value); + } + + /** + * Asserts a value is a scalar (string, int, float, or bool) for sprintf. + * + * @param mixed $value + * @return string|int|float|bool + */ + protected static function asScalar($value) + { + if (!is_scalar($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected scalar value, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Asserts a value is a string or number (int/float) and returns it. + * + * Used for MCP types like ProgressToken that accept string | number. + * + * @param mixed $value + * @return string|int|float + */ + protected static function asStringOrNumber($value) + { + if (!is_string($value) && !is_int($value) && !is_float($value)) { + throw new \InvalidArgumentException(sprintf( + 'Expected string or number, got %s', + gettype($value) + )); + } + return $value; + } + + /** + * Returns a value as string or number (int/float), or null. + * + * Used for optional MCP types like ProgressToken that accept string | number | null. + * + * @param mixed $value + * @return string|int|float|null + */ + protected static function asStringOrNumberOrNull($value) + { + return $value === null ? null : self::asStringOrNumber($value); + } +} diff --git a/src/Server/Core/CompleteRequest.php b/src/Server/Core/CompleteRequest.php new file mode 100644 index 0000000..6b24d8a --- /dev/null +++ b/src/Server/Core/CompleteRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'completion/complete', + * params: array|\WP\McpSchema\Server\Core\CompleteRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Core\CompleteRequestParams $params */ + $params = is_array($data['params']) + ? CompleteRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Core\CompleteRequestParams + */ + public function getTypedParams(): CompleteRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Core/CompleteRequestParams.php b/src/Server/Core/CompleteRequestParams.php new file mode 100644 index 0000000..294e67c --- /dev/null +++ b/src/Server/Core/CompleteRequestParams.php @@ -0,0 +1,154 @@ +ref = $ref; + $this->argument = $argument; + $this->context = $context; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * ref: \WP\McpSchema\Server\Core\PromptReference|\WP\McpSchema\Server\Core\ResourceTemplateReference, + * argument: array|\WP\McpSchema\Server\Core\CompleteRequestParamsArgument, + * context?: array|\WP\McpSchema\Server\Core\CompleteRequestParamsContext|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['ref', 'argument']); + + /** @var \WP\McpSchema\Server\Core\PromptReference|\WP\McpSchema\Server\Core\ResourceTemplateReference $ref */ + $ref = $data['ref']; + + /** @var \WP\McpSchema\Server\Core\CompleteRequestParamsArgument $argument */ + $argument = is_array($data['argument']) + ? CompleteRequestParamsArgument::fromArray(self::asArray($data['argument'])) + : $data['argument']; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + /** @var \WP\McpSchema\Server\Core\CompleteRequestParamsContext|null $context */ + $context = isset($data['context']) + ? (is_array($data['context']) + ? CompleteRequestParamsContext::fromArray(self::asArray($data['context'])) + : $data['context']) + : null; + + return new self( + $ref, + $argument, + $_meta, + $context + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['ref'] = (is_object($this->ref) && method_exists($this->ref, 'toArray')) ? $this->ref->toArray() : $this->ref; + $result['argument'] = $this->argument->toArray(); + if ($this->context !== null) { + $result['context'] = $this->context->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Core\PromptReference|\WP\McpSchema\Server\Core\ResourceTemplateReference + */ + public function getRef() + { + return $this->ref; + } + + /** + * @return \WP\McpSchema\Server\Core\CompleteRequestParamsArgument + */ + public function getArgument(): CompleteRequestParamsArgument + { + return $this->argument; + } + + /** + * @return \WP\McpSchema\Server\Core\CompleteRequestParamsContext|null + */ + public function getContext(): ?CompleteRequestParamsContext + { + return $this->context; + } +} diff --git a/src/Server/Core/CompleteRequestParamsArgument.php b/src/Server/Core/CompleteRequestParamsArgument.php new file mode 100644 index 0000000..fc53371 --- /dev/null +++ b/src/Server/Core/CompleteRequestParamsArgument.php @@ -0,0 +1,97 @@ +name = $name; + $this->value = $value; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * value: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name', 'value']); + + return new self( + self::asString($data['name']), + self::asString($data['value']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['name'] = $this->name; + $result['value'] = $this->value; + + return $result; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/src/Server/Core/CompleteRequestParamsContext.php b/src/Server/Core/CompleteRequestParamsContext.php new file mode 100644 index 0000000..6f986a9 --- /dev/null +++ b/src/Server/Core/CompleteRequestParamsContext.php @@ -0,0 +1,76 @@ +|null + */ + protected ?array $arguments; + + /** + * @param array|null $arguments + */ + public function __construct( + ?array $arguments = null + ) { + $this->arguments = $arguments; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * arguments?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringMapOrNull($data['arguments'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->arguments !== null) { + $result['arguments'] = $this->arguments; + } + + return $result; + } + + /** + * @return array|null + */ + public function getArguments(): ?array + { + return $this->arguments; + } +} diff --git a/src/Server/Core/CompleteResult.php b/src/Server/Core/CompleteResult.php new file mode 100644 index 0000000..c4097fd --- /dev/null +++ b/src/Server/Core/CompleteResult.php @@ -0,0 +1,89 @@ +|null $_meta @since 2024-11-05 + */ + public function __construct( + CompleteResultCompletion $completion, + ?array $_meta = null + ) { + parent::__construct($_meta); + $this->completion = $completion; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * completion: array|\WP\McpSchema\Server\Core\CompleteResultCompletion + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['completion']); + + /** @var \WP\McpSchema\Server\Core\CompleteResultCompletion $completion */ + $completion = is_array($data['completion']) + ? CompleteResultCompletion::fromArray(self::asArray($data['completion'])) + : $data['completion']; + + return new self( + $completion, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['completion'] = $this->completion->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Core\CompleteResultCompletion + */ + public function getCompletion(): CompleteResultCompletion + { + return $this->completion; + } +} diff --git a/src/Server/Core/CompleteResultCompletion.php b/src/Server/Core/CompleteResultCompletion.php new file mode 100644 index 0000000..ab457eb --- /dev/null +++ b/src/Server/Core/CompleteResultCompletion.php @@ -0,0 +1,132 @@ + + */ + protected array $values; + + /** + * The total number of completion options available. This can exceed the number of values actually sent in the response. + * + * @var int|null + */ + protected ?int $total; + + /** + * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + * + * @var bool|null + */ + protected ?bool $hasMore; + + /** + * @param array $values + * @param int|null $total + * @param bool|null $hasMore + */ + public function __construct( + array $values, + ?int $total = null, + ?bool $hasMore = null + ) { + $this->values = $values; + $this->total = $total; + $this->hasMore = $hasMore; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * values: array, + * total?: int|null, + * hasMore?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['values']); + + if (is_array($data['values']) && count($data['values']) > self::MAX_VALUES) { + throw new \InvalidArgumentException(sprintf( + '%s::values must not exceed %d items, got %d', + static::class, + self::MAX_VALUES, + count($data['values']) + )); + } + + return new self( + self::asStringArray($data['values']), + self::asIntOrNull($data['total'] ?? null), + self::asBoolOrNull($data['hasMore'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['values'] = $this->values; + if ($this->total !== null) { + $result['total'] = $this->total; + } + if ($this->hasMore !== null) { + $result['hasMore'] = $this->hasMore; + } + + return $result; + } + + /** + * @return array + */ + public function getValues(): array + { + return $this->values; + } + + /** + * @return int|null + */ + public function getTotal(): ?int + { + return $this->total; + } + + /** + * @return bool|null + */ + public function getHasMore(): ?bool + { + return $this->hasMore; + } +} diff --git a/src/Server/Core/PromptReference.php b/src/Server/Core/PromptReference.php new file mode 100644 index 0000000..6a9ba8b --- /dev/null +++ b/src/Server/Core/PromptReference.php @@ -0,0 +1,87 @@ +type = self::TYPE; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * type: 'ref/prompt' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + return new self( + self::asString($data['name']), + self::asStringOrNull($data['title'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['type'] = $this->type; + + return $result; + } + + /** + * @return 'ref/prompt' + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/src/Server/Core/ResourceTemplateReference.php b/src/Server/Core/ResourceTemplateReference.php new file mode 100644 index 0000000..c765cc5 --- /dev/null +++ b/src/Server/Core/ResourceTemplateReference.php @@ -0,0 +1,100 @@ +type = self::TYPE; + $this->uri = $uri; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * type: 'ref/resource', + * uri: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + return new self( + self::asString($data['uri']) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['type'] = $this->type; + $result['uri'] = $this->uri; + + return $result; + } + + /** + * @return 'ref/resource' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } +} diff --git a/src/Server/Lifecycle/Factory/ServerNotificationFactory.php b/src/Server/Lifecycle/Factory/ServerNotificationFactory.php new file mode 100644 index 0000000..92c0cf3 --- /dev/null +++ b/src/Server/Lifecycle/Factory/ServerNotificationFactory.php @@ -0,0 +1,91 @@ +> + */ + public const REGISTRY = [ + 'notifications/cancelled' => CancelledNotification::class, + 'notifications/progress' => ProgressNotification::class, + 'notifications/message' => LoggingMessageNotification::class, + 'notifications/resources/updated' => ResourceUpdatedNotification::class, + 'notifications/resources/list_changed' => ResourceListChangedNotification::class, + 'notifications/tools/list_changed' => ToolListChangedNotification::class, + 'notifications/prompts/list_changed' => PromptListChangedNotification::class, + 'notifications/elicitation/complete' => ElicitationCompleteNotification::class, + 'notifications/tasks/status' => TaskStatusNotification::class, + ]; + + /** + * Creates an instance from an array. + * + * @param array $data + * @return ServerNotificationInterface + * @throws \InvalidArgumentException + */ + public static function fromArray(array $data): ServerNotificationInterface + { + if (!isset($data['method'])) { + throw new \InvalidArgumentException('Missing discriminator field: method'); + } + + /** @var string $method */ + $method = $data['method']; + if (!isset(self::REGISTRY[$method])) { + throw new \InvalidArgumentException(sprintf( + "Unknown method value '%s'. Valid values: %s", + $method, + implode(', ', array_keys(self::REGISTRY)) + )); + } + + $class = self::REGISTRY[$method]; + return $class::fromArray($data); + } + + /** + * Checks if a method value is supported by this factory. + * + * @param string $method + * @return bool + */ + public static function supports(string $method): bool + { + return isset(self::REGISTRY[$method]); + } + + /** + * Returns all supported method values. + * + * @return array + */ + public static function methods(): array + { + return array_keys(self::REGISTRY); + } +} diff --git a/src/Server/Lifecycle/ServerCapabilities.php b/src/Server/Lifecycle/ServerCapabilities.php new file mode 100644 index 0000000..aa18ee4 --- /dev/null +++ b/src/Server/Lifecycle/ServerCapabilities.php @@ -0,0 +1,259 @@ +|null + */ + protected ?array $experimental; + + /** + * Present if the server supports sending log messages to the client. + * + * @since 2024-11-05 + * + * @var object|null + */ + protected ?object $logging; + + /** + * Present if the server supports argument autocompletion suggestions. + * + * @since 2025-03-26 + * + * @var object|null + */ + protected ?object $completions; + + /** + * Present if the server offers any prompt templates. + * + * @since 2024-11-05 + * + * @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesPrompts|null + */ + protected ?ServerCapabilitiesPrompts $prompts; + + /** + * Present if the server offers any resources to read. + * + * @since 2024-11-05 + * + * @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesResources|null + */ + protected ?ServerCapabilitiesResources $resources; + + /** + * Present if the server offers any tools to call. + * + * @since 2024-11-05 + * + * @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTools|null + */ + protected ?ServerCapabilitiesTools $tools; + + /** + * Present if the server supports task-augmented requests. + * + * @since 2025-11-25 + * + * @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTasks|null + */ + protected ?ServerCapabilitiesTasks $tasks; + + /** + * @param array|null $experimental @since 2024-11-05 + * @param object|null $logging @since 2024-11-05 + * @param object|null $completions @since 2025-03-26 + * @param \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesPrompts|null $prompts @since 2024-11-05 + * @param \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesResources|null $resources @since 2024-11-05 + * @param \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTools|null $tools @since 2024-11-05 + * @param \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTasks|null $tasks @since 2025-11-25 + */ + public function __construct( + ?array $experimental = null, + ?object $logging = null, + ?object $completions = null, + ?ServerCapabilitiesPrompts $prompts = null, + ?ServerCapabilitiesResources $resources = null, + ?ServerCapabilitiesTools $tools = null, + ?ServerCapabilitiesTasks $tasks = null + ) { + $this->experimental = $experimental; + $this->logging = $logging; + $this->completions = $completions; + $this->prompts = $prompts; + $this->resources = $resources; + $this->tools = $tools; + $this->tasks = $tasks; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * experimental?: array|null, + * logging?: object|null, + * completions?: object|null, + * prompts?: array|\WP\McpSchema\Server\Lifecycle\ServerCapabilitiesPrompts|null, + * resources?: array|\WP\McpSchema\Server\Lifecycle\ServerCapabilitiesResources|null, + * tools?: array|\WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTools|null, + * tasks?: array|\WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTasks|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesPrompts|null $prompts */ + $prompts = isset($data['prompts']) + ? (is_array($data['prompts']) + ? ServerCapabilitiesPrompts::fromArray(self::asArray($data['prompts'])) + : $data['prompts']) + : null; + + /** @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesResources|null $resources */ + $resources = isset($data['resources']) + ? (is_array($data['resources']) + ? ServerCapabilitiesResources::fromArray(self::asArray($data['resources'])) + : $data['resources']) + : null; + + /** @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTools|null $tools */ + $tools = isset($data['tools']) + ? (is_array($data['tools']) + ? ServerCapabilitiesTools::fromArray(self::asArray($data['tools'])) + : $data['tools']) + : null; + + /** @var \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTasks|null $tasks */ + $tasks = isset($data['tasks']) + ? (is_array($data['tasks']) + ? ServerCapabilitiesTasks::fromArray(self::asArray($data['tasks'])) + : $data['tasks']) + : null; + + return new self( + self::asObjectMapOrNull($data['experimental'] ?? null), + self::asObjectOrNull($data['logging'] ?? null), + self::asObjectOrNull($data['completions'] ?? null), + $prompts, + $resources, + $tools, + $tasks + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->experimental !== null) { + $result['experimental'] = $this->experimental; + } + if ($this->logging !== null) { + $result['logging'] = $this->logging; + } + if ($this->completions !== null) { + $result['completions'] = $this->completions; + } + if ($this->prompts !== null) { + $result['prompts'] = $this->prompts->toArray(); + } + if ($this->resources !== null) { + $result['resources'] = $this->resources->toArray(); + } + if ($this->tools !== null) { + $result['tools'] = $this->tools->toArray(); + } + if ($this->tasks !== null) { + $result['tasks'] = $this->tasks->toArray(); + } + + return $result; + } + + /** + * @return array|null + */ + public function getExperimental(): ?array + { + return $this->experimental; + } + + /** + * @return object|null + */ + public function getLogging(): ?object + { + return $this->logging; + } + + /** + * @return object|null + */ + public function getCompletions(): ?object + { + return $this->completions; + } + + /** + * @return \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesPrompts|null + */ + public function getPrompts(): ?ServerCapabilitiesPrompts + { + return $this->prompts; + } + + /** + * @return \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesResources|null + */ + public function getResources(): ?ServerCapabilitiesResources + { + return $this->resources; + } + + /** + * @return \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTools|null + */ + public function getTools(): ?ServerCapabilitiesTools + { + return $this->tools; + } + + /** + * @return \WP\McpSchema\Server\Lifecycle\ServerCapabilitiesTasks|null + */ + public function getTasks(): ?ServerCapabilitiesTasks + { + return $this->tasks; + } +} diff --git a/src/Server/Lifecycle/ServerCapabilitiesPrompts.php b/src/Server/Lifecycle/ServerCapabilitiesPrompts.php new file mode 100644 index 0000000..35f71bd --- /dev/null +++ b/src/Server/Lifecycle/ServerCapabilitiesPrompts.php @@ -0,0 +1,76 @@ +listChanged = $listChanged; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * listChanged?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asBoolOrNull($data['listChanged'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->listChanged !== null) { + $result['listChanged'] = $this->listChanged; + } + + return $result; + } + + /** + * @return bool|null + */ + public function getListChanged(): ?bool + { + return $this->listChanged; + } +} diff --git a/src/Server/Lifecycle/ServerCapabilitiesResources.php b/src/Server/Lifecycle/ServerCapabilitiesResources.php new file mode 100644 index 0000000..b1005d4 --- /dev/null +++ b/src/Server/Lifecycle/ServerCapabilitiesResources.php @@ -0,0 +1,99 @@ +subscribe = $subscribe; + $this->listChanged = $listChanged; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * subscribe?: bool|null, + * listChanged?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asBoolOrNull($data['subscribe'] ?? null), + self::asBoolOrNull($data['listChanged'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->subscribe !== null) { + $result['subscribe'] = $this->subscribe; + } + if ($this->listChanged !== null) { + $result['listChanged'] = $this->listChanged; + } + + return $result; + } + + /** + * @return bool|null + */ + public function getSubscribe(): ?bool + { + return $this->subscribe; + } + + /** + * @return bool|null + */ + public function getListChanged(): ?bool + { + return $this->listChanged; + } +} diff --git a/src/Server/Lifecycle/ServerCapabilitiesTasks.php b/src/Server/Lifecycle/ServerCapabilitiesTasks.php new file mode 100644 index 0000000..9926bdd --- /dev/null +++ b/src/Server/Lifecycle/ServerCapabilitiesTasks.php @@ -0,0 +1,99 @@ +list = $list; + $this->cancel = $cancel; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * list?: object|null, + * cancel?: object|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asObjectOrNull($data['list'] ?? null), + self::asObjectOrNull($data['cancel'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->list !== null) { + $result['list'] = $this->list; + } + if ($this->cancel !== null) { + $result['cancel'] = $this->cancel; + } + + return $result; + } + + /** + * @return object|null + */ + public function getList(): ?object + { + return $this->list; + } + + /** + * @return object|null + */ + public function getCancel(): ?object + { + return $this->cancel; + } +} diff --git a/src/Server/Lifecycle/ServerCapabilitiesTools.php b/src/Server/Lifecycle/ServerCapabilitiesTools.php new file mode 100644 index 0000000..308810f --- /dev/null +++ b/src/Server/Lifecycle/ServerCapabilitiesTools.php @@ -0,0 +1,76 @@ +listChanged = $listChanged; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * listChanged?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asBoolOrNull($data['listChanged'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->listChanged !== null) { + $result['listChanged'] = $this->listChanged; + } + + return $result; + } + + /** + * @return bool|null + */ + public function getListChanged(): ?bool + { + return $this->listChanged; + } +} diff --git a/src/Server/Lifecycle/Union/ServerNotificationInterface.php b/src/Server/Lifecycle/Union/ServerNotificationInterface.php new file mode 100644 index 0000000..e349023 --- /dev/null +++ b/src/Server/Lifecycle/Union/ServerNotificationInterface.php @@ -0,0 +1,31 @@ + + */ + public function toArray(): array; +} diff --git a/src/Server/Lifecycle/Union/ServerResultInterface.php b/src/Server/Lifecycle/Union/ServerResultInterface.php new file mode 100644 index 0000000..5f4df91 --- /dev/null +++ b/src/Server/Lifecycle/Union/ServerResultInterface.php @@ -0,0 +1,36 @@ + + */ + public function toArray(): array; +} diff --git a/src/Server/Logging/Enum/LoggingLevel.php b/src/Server/Logging/Enum/LoggingLevel.php new file mode 100644 index 0000000..f1df99e --- /dev/null +++ b/src/Server/Logging/Enum/LoggingLevel.php @@ -0,0 +1,88 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/message', + * params: array|\WP\McpSchema\Server\Logging\LoggingMessageNotificationParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Server\Logging\LoggingMessageNotificationParams $params */ + $params = is_array($data['params']) + ? LoggingMessageNotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Logging\LoggingMessageNotificationParams + */ + public function getTypedParams(): LoggingMessageNotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Logging/LoggingMessageNotificationParams.php b/src/Server/Logging/LoggingMessageNotificationParams.php new file mode 100644 index 0000000..b121240 --- /dev/null +++ b/src/Server/Logging/LoggingMessageNotificationParams.php @@ -0,0 +1,136 @@ +|null $_meta @since 2025-11-25 + * @param string|null $logger @since 2025-11-25 + */ + public function __construct( + string $level, + $data, + ?array $_meta = null, + ?string $logger = null + ) { + parent::__construct($_meta); + $this->level = $level; + $this->data = $data; + $this->logger = $logger; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * level: 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency', + * logger?: string|null, + * data: mixed + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['level', 'data']); + + /** @var 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency' $level */ + $level = self::asString($data['level']); + + return new self( + $level, + $data['data'], + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['logger'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['level'] = $this->level; + if ($this->logger !== null) { + $result['logger'] = $this->logger; + } + $result['data'] = $this->data; + + return $result; + } + + /** + * @return 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency' + */ + public function getLevel(): string + { + return $this->level; + } + + /** + * @return string|null + */ + public function getLogger(): ?string + { + return $this->logger; + } + + /** + * @return mixed + */ + public function getData() + { + return $this->data; + } +} diff --git a/src/Server/Logging/SetLevelRequest.php b/src/Server/Logging/SetLevelRequest.php new file mode 100644 index 0000000..ca78707 --- /dev/null +++ b/src/Server/Logging/SetLevelRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'logging/setLevel', + * params: array|\WP\McpSchema\Server\Logging\SetLevelRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Logging\SetLevelRequestParams $params */ + $params = is_array($data['params']) + ? SetLevelRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Logging\SetLevelRequestParams + */ + public function getTypedParams(): SetLevelRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Logging/SetLevelRequestParams.php b/src/Server/Logging/SetLevelRequestParams.php new file mode 100644 index 0000000..813aaab --- /dev/null +++ b/src/Server/Logging/SetLevelRequestParams.php @@ -0,0 +1,96 @@ +level = $level; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * level: 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['level']); + + /** @var 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency' $level */ + $level = self::asString($data['level']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + $level, + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['level'] = $this->level; + + return $result; + } + + /** + * @return 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency' + */ + public function getLevel(): string + { + return $this->level; + } +} diff --git a/src/Server/Prompts/GetPromptRequest.php b/src/Server/Prompts/GetPromptRequest.php new file mode 100644 index 0000000..b1e998d --- /dev/null +++ b/src/Server/Prompts/GetPromptRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'prompts/get', + * params: array|\WP\McpSchema\Server\Prompts\GetPromptRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Prompts\GetPromptRequestParams $params */ + $params = is_array($data['params']) + ? GetPromptRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Prompts\GetPromptRequestParams + */ + public function getTypedParams(): GetPromptRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Prompts/GetPromptRequestParams.php b/src/Server/Prompts/GetPromptRequestParams.php new file mode 100644 index 0000000..ccaa7ef --- /dev/null +++ b/src/Server/Prompts/GetPromptRequestParams.php @@ -0,0 +1,118 @@ +|null + */ + protected ?array $arguments; + + /** + * @param string $name @since 2025-11-25 + * @param \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta @since 2025-11-25 + * @param array|null $arguments @since 2025-11-25 + */ + public function __construct( + string $name, + ?RequestParamsMeta $_meta = null, + ?array $arguments = null + ) { + parent::__construct($_meta); + $this->name = $name; + $this->arguments = $arguments; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * name: string, + * arguments?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['name']), + $_meta, + self::asStringMapOrNull($data['arguments'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['name'] = $this->name; + if ($this->arguments !== null) { + $result['arguments'] = $this->arguments; + } + + return $result; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return array|null + */ + public function getArguments(): ?array + { + return $this->arguments; + } +} diff --git a/src/Server/Prompts/GetPromptResult.php b/src/Server/Prompts/GetPromptResult.php new file mode 100644 index 0000000..58c749b --- /dev/null +++ b/src/Server/Prompts/GetPromptResult.php @@ -0,0 +1,118 @@ + + */ + protected array $messages; + + /** + * @param array<\WP\McpSchema\Server\Prompts\PromptMessage> $messages @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + * @param string|null $description @since 2024-11-05 + */ + public function __construct( + array $messages, + ?array $_meta = null, + ?string $description = null + ) { + parent::__construct($_meta); + $this->messages = $messages; + $this->description = $description; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * description?: string|null, + * messages: array|\WP\McpSchema\Server\Prompts\PromptMessage> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['messages']); + + /** @var array<\WP\McpSchema\Server\Prompts\PromptMessage> $messages */ + $messages = array_map( + static fn($item) => is_array($item) + ? PromptMessage::fromArray($item) + : $item, + self::asArray($data['messages']) + ); + + return new self( + $messages, + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['description'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->description !== null) { + $result['description'] = $this->description; + } + $result['messages'] = array_map(static fn($item) => $item->toArray(), $this->messages); + + return $result; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return array<\WP\McpSchema\Server\Prompts\PromptMessage> + */ + public function getMessages(): array + { + return $this->messages; + } +} diff --git a/src/Server/Prompts/ListPromptsRequest.php b/src/Server/Prompts/ListPromptsRequest.php new file mode 100644 index 0000000..7ea5380 --- /dev/null +++ b/src/Server/Prompts/ListPromptsRequest.php @@ -0,0 +1,95 @@ +|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null, + * jsonrpc: '2.0', + * id: string|number, + * method: 'prompts/list' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Prompts/ListPromptsResult.php b/src/Server/Prompts/ListPromptsResult.php new file mode 100644 index 0000000..4b19b8f --- /dev/null +++ b/src/Server/Prompts/ListPromptsResult.php @@ -0,0 +1,97 @@ + + */ + protected array $prompts; + + /** + * @param array<\WP\McpSchema\Server\Prompts\Prompt> $prompts @since 2024-11-05 + * @param string|null $nextCursor @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $prompts, + ?string $nextCursor = null, + ?array $_meta = null + ) { + parent::__construct($_meta, $nextCursor); + $this->prompts = $prompts; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * nextCursor?: string|null, + * _meta?: array|null, + * prompts: array|\WP\McpSchema\Server\Prompts\Prompt> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['prompts']); + + /** @var array<\WP\McpSchema\Server\Prompts\Prompt> $prompts */ + $prompts = array_map( + static fn($item) => is_array($item) + ? Prompt::fromArray($item) + : $item, + self::asArray($data['prompts']) + ); + + return new self( + $prompts, + self::asStringOrNull($data['nextCursor'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['prompts'] = array_map(static fn($item) => $item->toArray(), $this->prompts); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Server\Prompts\Prompt> + */ + public function getPrompts(): array + { + return $this->prompts; + } +} diff --git a/src/Server/Prompts/Prompt.php b/src/Server/Prompts/Prompt.php new file mode 100644 index 0000000..0cf611b --- /dev/null +++ b/src/Server/Prompts/Prompt.php @@ -0,0 +1,196 @@ +|null + */ + protected ?array $arguments; + + /** + * See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + * + * @since 2025-06-18 + * + * @var array|null + */ + protected ?array $_meta; + + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following MIME types: + * - `image/png` - PNG images (safe, universal compatibility) + * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + * + * Clients that support rendering icons SHOULD also support: + * - `image/svg+xml` - SVG images (scalable but requires security precautions) + * - `image/webp` - WebP images (modern, efficient format) + * + * @since 2025-11-25 + * + * @var array<\WP\McpSchema\Common\Core\Icon>|null + */ + protected ?array $icons; + + /** + * @param string $name @since 2024-11-05 + * @param string|null $title @since 2025-06-18 + * @param string|null $description @since 2024-11-05 + * @param array<\WP\McpSchema\Server\Prompts\PromptArgument>|null $arguments @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $name, + ?string $title = null, + ?string $description = null, + ?array $arguments = null, + ?array $_meta = null, + ?array $icons = null + ) { + parent::__construct($name, $title); + $this->description = $description; + $this->arguments = $arguments; + $this->_meta = $_meta; + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * description?: string|null, + * arguments?: array|\WP\McpSchema\Server\Prompts\PromptArgument>|null, + * _meta?: array|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + /** @var array<\WP\McpSchema\Server\Prompts\PromptArgument>|null $arguments */ + $arguments = isset($data['arguments']) + ? array_map( + static fn($item) => is_array($item) + ? PromptArgument::fromArray($item) + : $item, + self::asArray($data['arguments']) + ) + : null; + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['name']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + $arguments, + self::asArrayOrNull($data['_meta'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->arguments !== null) { + $result['arguments'] = array_map(static fn($item) => $item->toArray(), $this->arguments); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return array<\WP\McpSchema\Server\Prompts\PromptArgument>|null + */ + public function getArguments(): ?array + { + return $this->arguments; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Server/Prompts/PromptArgument.php b/src/Server/Prompts/PromptArgument.php new file mode 100644 index 0000000..a0c44ee --- /dev/null +++ b/src/Server/Prompts/PromptArgument.php @@ -0,0 +1,117 @@ +description = $description; + $this->required = $required; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * description?: string|null, + * required?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + return new self( + self::asString($data['name']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asBoolOrNull($data['required'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->required !== null) { + $result['required'] = $this->required; + } + + return $result; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return bool|null + */ + public function getRequired(): ?bool + { + return $this->required; + } +} diff --git a/src/Server/Prompts/PromptListChangedNotification.php b/src/Server/Prompts/PromptListChangedNotification.php new file mode 100644 index 0000000..dbfaa25 --- /dev/null +++ b/src/Server/Prompts/PromptListChangedNotification.php @@ -0,0 +1,102 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/prompts/list_changed', + * params?: array|\WP\McpSchema\Common\JsonRpc\NotificationParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\NotificationParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? NotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\NotificationParams|null + */ + public function getTypedParams(): ?NotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Prompts/PromptMessage.php b/src/Server/Prompts/PromptMessage.php new file mode 100644 index 0000000..e3a5bd5 --- /dev/null +++ b/src/Server/Prompts/PromptMessage.php @@ -0,0 +1,113 @@ +role = $role; + $this->content = $content; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * role: 'user'|'assistant', + * content: array|\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['role', 'content']); + + /** @var 'user'|'assistant' $role */ + $role = self::asString($data['role']); + + /** @var \WP\McpSchema\Common\Protocol\Union\ContentBlockInterface $content */ + $content = is_array($data['content']) + ? ContentBlockFactory::fromArray(self::asArray($data['content'])) + : $data['content']; + + return new self( + $role, + $content + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['role'] = $this->role; + $result['content'] = $this->content->toArray(); + + return $result; + } + + /** + * @return 'user'|'assistant' + */ + public function getRole(): string + { + return $this->role; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Union\ContentBlockInterface + */ + public function getContent(): ContentBlockInterface + { + return $this->content; + } +} diff --git a/src/Server/Resources/ListResourceTemplatesRequest.php b/src/Server/Resources/ListResourceTemplatesRequest.php new file mode 100644 index 0000000..a04536f --- /dev/null +++ b/src/Server/Resources/ListResourceTemplatesRequest.php @@ -0,0 +1,95 @@ +|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null, + * jsonrpc: '2.0', + * id: string|number, + * method: 'resources/templates/list' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Resources/ListResourceTemplatesResult.php b/src/Server/Resources/ListResourceTemplatesResult.php new file mode 100644 index 0000000..9be8aa4 --- /dev/null +++ b/src/Server/Resources/ListResourceTemplatesResult.php @@ -0,0 +1,97 @@ + + */ + protected array $resourceTemplates; + + /** + * @param array<\WP\McpSchema\Server\Resources\ResourceTemplate> $resourceTemplates @since 2024-11-05 + * @param string|null $nextCursor @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $resourceTemplates, + ?string $nextCursor = null, + ?array $_meta = null + ) { + parent::__construct($_meta, $nextCursor); + $this->resourceTemplates = $resourceTemplates; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * nextCursor?: string|null, + * _meta?: array|null, + * resourceTemplates: array|\WP\McpSchema\Server\Resources\ResourceTemplate> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['resourceTemplates']); + + /** @var array<\WP\McpSchema\Server\Resources\ResourceTemplate> $resourceTemplates */ + $resourceTemplates = array_map( + static fn($item) => is_array($item) + ? ResourceTemplate::fromArray($item) + : $item, + self::asArray($data['resourceTemplates']) + ); + + return new self( + $resourceTemplates, + self::asStringOrNull($data['nextCursor'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['resourceTemplates'] = array_map(static fn($item) => $item->toArray(), $this->resourceTemplates); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Server\Resources\ResourceTemplate> + */ + public function getResourceTemplates(): array + { + return $this->resourceTemplates; + } +} diff --git a/src/Server/Resources/ListResourcesRequest.php b/src/Server/Resources/ListResourcesRequest.php new file mode 100644 index 0000000..21d7254 --- /dev/null +++ b/src/Server/Resources/ListResourcesRequest.php @@ -0,0 +1,95 @@ +|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null, + * jsonrpc: '2.0', + * id: string|number, + * method: 'resources/list' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Resources/ListResourcesResult.php b/src/Server/Resources/ListResourcesResult.php new file mode 100644 index 0000000..aeefa6f --- /dev/null +++ b/src/Server/Resources/ListResourcesResult.php @@ -0,0 +1,97 @@ + + */ + protected array $resources; + + /** + * @param array<\WP\McpSchema\Server\Resources\Resource> $resources @since 2024-11-05 + * @param string|null $nextCursor @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $resources, + ?string $nextCursor = null, + ?array $_meta = null + ) { + parent::__construct($_meta, $nextCursor); + $this->resources = $resources; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * nextCursor?: string|null, + * _meta?: array|null, + * resources: array|\WP\McpSchema\Server\Resources\Resource> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['resources']); + + /** @var array<\WP\McpSchema\Server\Resources\Resource> $resources */ + $resources = array_map( + static fn($item) => is_array($item) + ? Resource::fromArray($item) + : $item, + self::asArray($data['resources']) + ); + + return new self( + $resources, + self::asStringOrNull($data['nextCursor'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['resources'] = array_map(static fn($item) => $item->toArray(), $this->resources); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Server\Resources\Resource> + */ + public function getResources(): array + { + return $this->resources; + } +} diff --git a/src/Server/Resources/ReadResourceRequest.php b/src/Server/Resources/ReadResourceRequest.php new file mode 100644 index 0000000..9144f76 --- /dev/null +++ b/src/Server/Resources/ReadResourceRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'resources/read', + * params: array|\WP\McpSchema\Server\Resources\ReadResourceRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Resources\ReadResourceRequestParams $params */ + $params = is_array($data['params']) + ? ReadResourceRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Resources\ReadResourceRequestParams + */ + public function getTypedParams(): ReadResourceRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Resources/ReadResourceRequestParams.php b/src/Server/Resources/ReadResourceRequestParams.php new file mode 100644 index 0000000..c53857e --- /dev/null +++ b/src/Server/Resources/ReadResourceRequestParams.php @@ -0,0 +1,76 @@ +|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['uri']), + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Resources/ReadResourceResult.php b/src/Server/Resources/ReadResourceResult.php new file mode 100644 index 0000000..a9136c0 --- /dev/null +++ b/src/Server/Resources/ReadResourceResult.php @@ -0,0 +1,88 @@ + + */ + protected array $contents; + + /** + * @param array<\WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents> $contents @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $contents, + ?array $_meta = null + ) { + parent::__construct($_meta); + $this->contents = $contents; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * contents: array<\WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['contents']); + + /** @var array<\WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents> $contents */ + $contents = self::asArray($data['contents']); + + return new self( + $contents, + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['contents'] = array_map(static fn($item) => (is_object($item) && method_exists($item, 'toArray')) ? $item->toArray() : $item, $this->contents); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Common\Protocol\TextResourceContents|\WP\McpSchema\Common\Protocol\BlobResourceContents> + */ + public function getContents(): array + { + return $this->contents; + } +} diff --git a/src/Server/Resources/Resource.php b/src/Server/Resources/Resource.php new file mode 100644 index 0000000..db0a47d --- /dev/null +++ b/src/Server/Resources/Resource.php @@ -0,0 +1,271 @@ +|null + */ + protected ?array $_meta; + + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following MIME types: + * - `image/png` - PNG images (safe, universal compatibility) + * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + * + * Clients that support rendering icons SHOULD also support: + * - `image/svg+xml` - SVG images (scalable but requires security precautions) + * - `image/webp` - WebP images (modern, efficient format) + * + * @since 2025-11-25 + * + * @var array<\WP\McpSchema\Common\Core\Icon>|null + */ + protected ?array $icons; + + /** + * @param string $name @since 2024-11-05 + * @param string $uri @since 2024-11-05 + * @param string|null $title @since 2025-06-18 + * @param string|null $description @since 2024-11-05 + * @param string|null $mimeType @since 2024-11-05 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2024-11-05 + * @param int|null $size @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $name, + string $uri, + ?string $title = null, + ?string $description = null, + ?string $mimeType = null, + ?Annotations $annotations = null, + ?int $size = null, + ?array $_meta = null, + ?array $icons = null + ) { + parent::__construct($name, $title); + $this->uri = $uri; + $this->description = $description; + $this->mimeType = $mimeType; + $this->annotations = $annotations; + $this->size = $size; + $this->_meta = $_meta; + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * uri: string, + * description?: string|null, + * mimeType?: string|null, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * size?: int|null, + * _meta?: array|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name', 'uri']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['name']), + self::asString($data['uri']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['mimeType'] ?? null), + $annotations, + self::asIntOrNull($data['size'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['uri'] = $this->uri; + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->mimeType !== null) { + $result['mimeType'] = $this->mimeType; + } + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->size !== null) { + $result['size'] = $this->size; + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return string|null + */ + public function getMimeType(): ?string + { + return $this->mimeType; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return int|null + */ + public function getSize(): ?int + { + return $this->size; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Server/Resources/ResourceContents.php b/src/Server/Resources/ResourceContents.php new file mode 100644 index 0000000..bdda5fc --- /dev/null +++ b/src/Server/Resources/ResourceContents.php @@ -0,0 +1,131 @@ +|null + */ + protected ?array $_meta; + + /** + * @param string $uri @since 2024-11-05 + * @param string|null $mimeType @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + */ + public function __construct( + string $uri, + ?string $mimeType = null, + ?array $_meta = null + ) { + $this->uri = $uri; + $this->mimeType = $mimeType; + $this->_meta = $_meta; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * uri: string, + * mimeType?: string|null, + * _meta?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + return new self( + self::asString($data['uri']), + self::asStringOrNull($data['mimeType'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + $result['uri'] = $this->uri; + if ($this->mimeType !== null) { + $result['mimeType'] = $this->mimeType; + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + + return $result; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } + + /** + * @return string|null + */ + public function getMimeType(): ?string + { + return $this->mimeType; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } +} diff --git a/src/Server/Resources/ResourceLink.php b/src/Server/Resources/ResourceLink.php new file mode 100644 index 0000000..7f51ce4 --- /dev/null +++ b/src/Server/Resources/ResourceLink.php @@ -0,0 +1,139 @@ +|null $_meta @since 2025-06-18 + * @param string|null $title @since 2025-06-18 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $uri, + string $name, + ?string $description = null, + ?string $mimeType = null, + ?Annotations $annotations = null, + ?int $size = null, + ?array $_meta = null, + ?string $title = null, + ?array $icons = null + ) { + parent::__construct($name, $uri, $title, $description, $mimeType, $annotations, $size, $_meta, $icons); + $this->type = self::TYPE; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * uri: string, + * description?: string|null, + * mimeType?: string|null, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * size?: int|null, + * _meta?: array|null, + * name: string, + * title?: string|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null, + * type: 'resource_link' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri', 'name']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['uri']), + self::asString($data['name']), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['mimeType'] ?? null), + $annotations, + self::asIntOrNull($data['size'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null), + self::asStringOrNull($data['title'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['type'] = $this->type; + + return $result; + } + + /** + * @return 'resource_link' + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/src/Server/Resources/ResourceListChangedNotification.php b/src/Server/Resources/ResourceListChangedNotification.php new file mode 100644 index 0000000..78b2701 --- /dev/null +++ b/src/Server/Resources/ResourceListChangedNotification.php @@ -0,0 +1,102 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/resources/list_changed', + * params?: array|\WP\McpSchema\Common\JsonRpc\NotificationParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\NotificationParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? NotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\NotificationParams|null + */ + public function getTypedParams(): ?NotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Resources/ResourceRequestParams.php b/src/Server/Resources/ResourceRequestParams.php new file mode 100644 index 0000000..65dbb41 --- /dev/null +++ b/src/Server/Resources/ResourceRequestParams.php @@ -0,0 +1,93 @@ +uri = $uri; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * uri: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['uri']), + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['uri'] = $this->uri; + + return $result; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } +} diff --git a/src/Server/Resources/ResourceTemplate.php b/src/Server/Resources/ResourceTemplate.php new file mode 100644 index 0000000..6b57a9e --- /dev/null +++ b/src/Server/Resources/ResourceTemplate.php @@ -0,0 +1,244 @@ +|null + */ + protected ?array $_meta; + + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following MIME types: + * - `image/png` - PNG images (safe, universal compatibility) + * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + * + * Clients that support rendering icons SHOULD also support: + * - `image/svg+xml` - SVG images (scalable but requires security precautions) + * - `image/webp` - WebP images (modern, efficient format) + * + * @since 2025-11-25 + * + * @var array<\WP\McpSchema\Common\Core\Icon>|null + */ + protected ?array $icons; + + /** + * @param string $name @since 2024-11-05 + * @param string $uriTemplate @since 2024-11-05 + * @param string|null $title @since 2025-06-18 + * @param string|null $description @since 2024-11-05 + * @param string|null $mimeType @since 2024-11-05 + * @param \WP\McpSchema\Common\Protocol\Annotations|null $annotations @since 2024-11-05 + * @param array|null $_meta @since 2025-06-18 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $name, + string $uriTemplate, + ?string $title = null, + ?string $description = null, + ?string $mimeType = null, + ?Annotations $annotations = null, + ?array $_meta = null, + ?array $icons = null + ) { + parent::__construct($name, $title); + $this->uriTemplate = $uriTemplate; + $this->description = $description; + $this->mimeType = $mimeType; + $this->annotations = $annotations; + $this->_meta = $_meta; + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * uriTemplate: string, + * description?: string|null, + * mimeType?: string|null, + * annotations?: array|\WP\McpSchema\Common\Protocol\Annotations|null, + * _meta?: array|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name', 'uriTemplate']); + + /** @var \WP\McpSchema\Common\Protocol\Annotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? Annotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['name']), + self::asString($data['uriTemplate']), + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + self::asStringOrNull($data['mimeType'] ?? null), + $annotations, + self::asArrayOrNull($data['_meta'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['uriTemplate'] = $this->uriTemplate; + if ($this->description !== null) { + $result['description'] = $this->description; + } + if ($this->mimeType !== null) { + $result['mimeType'] = $this->mimeType; + } + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return string + */ + public function getUriTemplate(): string + { + return $this->uriTemplate; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return string|null + */ + public function getMimeType(): ?string + { + return $this->mimeType; + } + + /** + * @return \WP\McpSchema\Common\Protocol\Annotations|null + */ + public function getAnnotations(): ?Annotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Server/Resources/ResourceUpdatedNotification.php b/src/Server/Resources/ResourceUpdatedNotification.php new file mode 100644 index 0000000..466573e --- /dev/null +++ b/src/Server/Resources/ResourceUpdatedNotification.php @@ -0,0 +1,97 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/resources/updated', + * params: array|\WP\McpSchema\Server\Resources\ResourceUpdatedNotificationParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Server\Resources\ResourceUpdatedNotificationParams $params */ + $params = is_array($data['params']) + ? ResourceUpdatedNotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Resources\ResourceUpdatedNotificationParams + */ + public function getTypedParams(): ResourceUpdatedNotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Resources/ResourceUpdatedNotificationParams.php b/src/Server/Resources/ResourceUpdatedNotificationParams.php new file mode 100644 index 0000000..330f697 --- /dev/null +++ b/src/Server/Resources/ResourceUpdatedNotificationParams.php @@ -0,0 +1,85 @@ +|null $_meta @since 2025-11-25 + */ + public function __construct( + string $uri, + ?array $_meta = null + ) { + parent::__construct($_meta); + $this->uri = $uri; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * uri: string + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + return new self( + self::asString($data['uri']), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['uri'] = $this->uri; + + return $result; + } + + /** + * @return string + */ + public function getUri(): string + { + return $this->uri; + } +} diff --git a/src/Server/Resources/SubscribeRequest.php b/src/Server/Resources/SubscribeRequest.php new file mode 100644 index 0000000..179a2e8 --- /dev/null +++ b/src/Server/Resources/SubscribeRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'resources/subscribe', + * params: array|\WP\McpSchema\Server\Resources\SubscribeRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Resources\SubscribeRequestParams $params */ + $params = is_array($data['params']) + ? SubscribeRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Resources\SubscribeRequestParams + */ + public function getTypedParams(): SubscribeRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Resources/SubscribeRequestParams.php b/src/Server/Resources/SubscribeRequestParams.php new file mode 100644 index 0000000..f8f8974 --- /dev/null +++ b/src/Server/Resources/SubscribeRequestParams.php @@ -0,0 +1,76 @@ +|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['uri']), + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Resources/UnsubscribeRequest.php b/src/Server/Resources/UnsubscribeRequest.php new file mode 100644 index 0000000..a4de97a --- /dev/null +++ b/src/Server/Resources/UnsubscribeRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'resources/unsubscribe', + * params: array|\WP\McpSchema\Server\Resources\UnsubscribeRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Resources\UnsubscribeRequestParams $params */ + $params = is_array($data['params']) + ? UnsubscribeRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Resources\UnsubscribeRequestParams + */ + public function getTypedParams(): UnsubscribeRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Resources/UnsubscribeRequestParams.php b/src/Server/Resources/UnsubscribeRequestParams.php new file mode 100644 index 0000000..1036456 --- /dev/null +++ b/src/Server/Resources/UnsubscribeRequestParams.php @@ -0,0 +1,76 @@ +|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['uri']); + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['uri']), + $_meta + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Tools/CallToolRequest.php b/src/Server/Tools/CallToolRequest.php new file mode 100644 index 0000000..6808580 --- /dev/null +++ b/src/Server/Tools/CallToolRequest.php @@ -0,0 +1,104 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * id: string|number, + * method: 'tools/call', + * params: array|\WP\McpSchema\Server\Tools\CallToolRequestParams + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id', 'params']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Server\Tools\CallToolRequestParams $params */ + $params = is_array($data['params']) + ? CallToolRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['params'] = $this->typedParams->toArray(); + + return $result; + } + + /** + * @return \WP\McpSchema\Server\Tools\CallToolRequestParams + */ + public function getTypedParams(): CallToolRequestParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Tools/CallToolRequestParams.php b/src/Server/Tools/CallToolRequestParams.php new file mode 100644 index 0000000..f48bdbe --- /dev/null +++ b/src/Server/Tools/CallToolRequestParams.php @@ -0,0 +1,130 @@ +|null + */ + protected ?array $arguments; + + /** + * @param string $name @since 2025-11-25 + * @param \WP\McpSchema\Client\Tasks\TaskMetadata|null $task @since 2025-11-25 + * @param \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta @since 2025-11-25 + * @param array|null $arguments @since 2025-11-25 + */ + public function __construct( + string $name, + ?TaskMetadata $task = null, + ?RequestParamsMeta $_meta = null, + ?array $arguments = null + ) { + parent::__construct($_meta, $task); + $this->name = $name; + $this->arguments = $arguments; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * task?: array|\WP\McpSchema\Client\Tasks\TaskMetadata|null, + * _meta?: array|\WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null, + * name: string, + * arguments?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name']); + + /** @var \WP\McpSchema\Client\Tasks\TaskMetadata|null $task */ + $task = isset($data['task']) + ? (is_array($data['task']) + ? TaskMetadata::fromArray(self::asArray($data['task'])) + : $data['task']) + : null; + + /** @var \WP\McpSchema\Common\JsonRpc\RequestParamsMeta|null $_meta */ + $_meta = isset($data['_meta']) + ? (is_array($data['_meta']) + ? RequestParamsMeta::fromArray(self::asArray($data['_meta'])) + : $data['_meta']) + : null; + + return new self( + self::asString($data['name']), + $task, + $_meta, + self::asArrayOrNull($data['arguments'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['name'] = $this->name; + if ($this->arguments !== null) { + $result['arguments'] = $this->arguments; + } + + return $result; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return array|null + */ + public function getArguments(): ?array + { + return $this->arguments; + } +} diff --git a/src/Server/Tools/CallToolResult.php b/src/Server/Tools/CallToolResult.php new file mode 100644 index 0000000..a1b135b --- /dev/null +++ b/src/Server/Tools/CallToolResult.php @@ -0,0 +1,158 @@ + + */ + protected array $content; + + /** + * An optional JSON object that represents the structured result of the tool call. + * + * @since 2025-06-18 + * + * @var array|null + */ + protected ?array $structuredContent; + + /** + * Whether the tool call ended in an error. + * + * If not set, this is assumed to be false (the call was successful). + * + * Any errors that originate from the tool SHOULD be reported inside the result + * object, with `isError` set to true, _not_ as an MCP protocol-level error + * response. Otherwise, the LLM would not be able to see that an error occurred + * and self-correct. + * + * However, any errors in _finding_ the tool, an error indicating that the + * server does not support tool calls, or any other exceptional conditions, + * should be reported as an MCP error response. + * + * @since 2024-11-05 + * + * @var bool|null + */ + protected ?bool $isError; + + /** + * @param array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> $content @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + * @param array|null $structuredContent @since 2025-06-18 + * @param bool|null $isError @since 2024-11-05 + */ + public function __construct( + array $content, + ?array $_meta = null, + ?array $structuredContent = null, + ?bool $isError = null + ) { + parent::__construct($_meta); + $this->content = $content; + $this->structuredContent = $structuredContent; + $this->isError = $isError; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * _meta?: array|null, + * content: array|\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface>, + * structuredContent?: array|null, + * isError?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['content']); + + /** @var array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> $content */ + $content = array_map( + static fn($item) => is_array($item) + ? ContentBlockFactory::fromArray($item) + : $item, + self::asArray($data['content']) + ); + + return new self( + $content, + self::asArrayOrNull($data['_meta'] ?? null), + self::asArrayOrNull($data['structuredContent'] ?? null), + self::asBoolOrNull($data['isError'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['content'] = array_map(static fn($item) => $item->toArray(), $this->content); + if ($this->structuredContent !== null) { + $result['structuredContent'] = $this->structuredContent; + } + if ($this->isError !== null) { + $result['isError'] = $this->isError; + } + + return $result; + } + + /** + * @return array<\WP\McpSchema\Common\Protocol\Union\ContentBlockInterface> + */ + public function getContent(): array + { + return $this->content; + } + + /** + * @return array|null + */ + public function getStructuredContent(): ?array + { + return $this->structuredContent; + } + + /** + * @return bool|null + */ + public function getIsError(): ?bool + { + return $this->isError; + } +} diff --git a/src/Server/Tools/ListToolsRequest.php b/src/Server/Tools/ListToolsRequest.php new file mode 100644 index 0000000..8c079ba --- /dev/null +++ b/src/Server/Tools/ListToolsRequest.php @@ -0,0 +1,95 @@ +|\WP\McpSchema\Common\Protocol\PaginatedRequestParams|null, + * jsonrpc: '2.0', + * id: string|number, + * method: 'tools/list' + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc', 'id']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var string|number $id */ + $id = self::asStringOrNumber($data['id']); + + /** @var \WP\McpSchema\Common\Protocol\PaginatedRequestParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? PaginatedRequestParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $id, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + return $result; + } +} diff --git a/src/Server/Tools/ListToolsResult.php b/src/Server/Tools/ListToolsResult.php new file mode 100644 index 0000000..b005355 --- /dev/null +++ b/src/Server/Tools/ListToolsResult.php @@ -0,0 +1,97 @@ + + */ + protected array $tools; + + /** + * @param array<\WP\McpSchema\Server\Tools\Tool> $tools @since 2024-11-05 + * @param string|null $nextCursor @since 2024-11-05 + * @param array|null $_meta @since 2024-11-05 + */ + public function __construct( + array $tools, + ?string $nextCursor = null, + ?array $_meta = null + ) { + parent::__construct($_meta, $nextCursor); + $this->tools = $tools; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * nextCursor?: string|null, + * _meta?: array|null, + * tools: array|\WP\McpSchema\Server\Tools\Tool> + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['tools']); + + /** @var array<\WP\McpSchema\Server\Tools\Tool> $tools */ + $tools = array_map( + static fn($item) => is_array($item) + ? Tool::fromArray($item) + : $item, + self::asArray($data['tools']) + ); + + return new self( + $tools, + self::asStringOrNull($data['nextCursor'] ?? null), + self::asArrayOrNull($data['_meta'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result['tools'] = array_map(static fn($item) => $item->toArray(), $this->tools); + + return $result; + } + + /** + * @return array<\WP\McpSchema\Server\Tools\Tool> + */ + public function getTools(): array + { + return $this->tools; + } +} diff --git a/src/Server/Tools/Tool.php b/src/Server/Tools/Tool.php new file mode 100644 index 0000000..524db4a --- /dev/null +++ b/src/Server/Tools/Tool.php @@ -0,0 +1,293 @@ +|null + */ + protected ?array $_meta; + + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following MIME types: + * - `image/png` - PNG images (safe, universal compatibility) + * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + * + * Clients that support rendering icons SHOULD also support: + * - `image/svg+xml` - SVG images (scalable but requires security precautions) + * - `image/webp` - WebP images (modern, efficient format) + * + * @since 2025-11-25 + * + * @var array<\WP\McpSchema\Common\Core\Icon>|null + */ + protected ?array $icons; + + /** + * @param string $name @since 2024-11-05 + * @param \WP\McpSchema\Server\Tools\ToolInputSchema $inputSchema @since 2024-11-05 + * @param string|null $title @since 2025-06-18 + * @param string|null $description @since 2024-11-05 + * @param \WP\McpSchema\Server\Tools\ToolExecution|null $execution @since 2025-11-25 + * @param \WP\McpSchema\Server\Tools\ToolOutputSchema|null $outputSchema @since 2025-06-18 + * @param \WP\McpSchema\Server\Tools\ToolAnnotations|null $annotations @since 2025-03-26 + * @param array|null $_meta @since 2025-06-18 + * @param array<\WP\McpSchema\Common\Core\Icon>|null $icons @since 2025-11-25 + */ + public function __construct( + string $name, + ToolInputSchema $inputSchema, + ?string $title = null, + ?string $description = null, + ?ToolExecution $execution = null, + ?ToolOutputSchema $outputSchema = null, + ?ToolAnnotations $annotations = null, + ?array $_meta = null, + ?array $icons = null + ) { + parent::__construct($name, $title); + $this->inputSchema = $inputSchema; + $this->description = $description; + $this->execution = $execution; + $this->outputSchema = $outputSchema; + $this->annotations = $annotations; + $this->_meta = $_meta; + $this->icons = $icons; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * name: string, + * title?: string|null, + * description?: string|null, + * inputSchema: array|\WP\McpSchema\Server\Tools\ToolInputSchema, + * execution?: array|\WP\McpSchema\Server\Tools\ToolExecution|null, + * outputSchema?: array|\WP\McpSchema\Server\Tools\ToolOutputSchema|null, + * annotations?: array|\WP\McpSchema\Server\Tools\ToolAnnotations|null, + * _meta?: array|null, + * icons?: array|\WP\McpSchema\Common\Core\Icon>|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['name', 'inputSchema']); + + /** @var \WP\McpSchema\Server\Tools\ToolInputSchema $inputSchema */ + $inputSchema = is_array($data['inputSchema']) + ? ToolInputSchema::fromArray(self::asArray($data['inputSchema'])) + : $data['inputSchema']; + + /** @var \WP\McpSchema\Server\Tools\ToolExecution|null $execution */ + $execution = isset($data['execution']) + ? (is_array($data['execution']) + ? ToolExecution::fromArray(self::asArray($data['execution'])) + : $data['execution']) + : null; + + /** @var \WP\McpSchema\Server\Tools\ToolOutputSchema|null $outputSchema */ + $outputSchema = isset($data['outputSchema']) + ? (is_array($data['outputSchema']) + ? ToolOutputSchema::fromArray(self::asArray($data['outputSchema'])) + : $data['outputSchema']) + : null; + + /** @var \WP\McpSchema\Server\Tools\ToolAnnotations|null $annotations */ + $annotations = isset($data['annotations']) + ? (is_array($data['annotations']) + ? ToolAnnotations::fromArray(self::asArray($data['annotations'])) + : $data['annotations']) + : null; + + /** @var array<\WP\McpSchema\Common\Core\Icon>|null $icons */ + $icons = isset($data['icons']) + ? array_map( + static fn($item) => is_array($item) + ? Icon::fromArray($item) + : $item, + self::asArray($data['icons']) + ) + : null; + + return new self( + self::asString($data['name']), + $inputSchema, + self::asStringOrNull($data['title'] ?? null), + self::asStringOrNull($data['description'] ?? null), + $execution, + $outputSchema, + $annotations, + self::asArrayOrNull($data['_meta'] ?? null), + $icons + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->description !== null) { + $result['description'] = $this->description; + } + $result['inputSchema'] = $this->inputSchema->toArray(); + if ($this->execution !== null) { + $result['execution'] = $this->execution->toArray(); + } + if ($this->outputSchema !== null) { + $result['outputSchema'] = $this->outputSchema->toArray(); + } + if ($this->annotations !== null) { + $result['annotations'] = $this->annotations->toArray(); + } + if ($this->_meta !== null) { + $result['_meta'] = $this->_meta; + } + if ($this->icons !== null) { + $result['icons'] = array_map(static fn($item) => $item->toArray(), $this->icons); + } + + return $result; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return \WP\McpSchema\Server\Tools\ToolInputSchema + */ + public function getInputSchema(): ToolInputSchema + { + return $this->inputSchema; + } + + /** + * @return \WP\McpSchema\Server\Tools\ToolExecution|null + */ + public function getExecution(): ?ToolExecution + { + return $this->execution; + } + + /** + * @return \WP\McpSchema\Server\Tools\ToolOutputSchema|null + */ + public function getOutputSchema(): ?ToolOutputSchema + { + return $this->outputSchema; + } + + /** + * @return \WP\McpSchema\Server\Tools\ToolAnnotations|null + */ + public function getAnnotations(): ?ToolAnnotations + { + return $this->annotations; + } + + /** + * @return array|null + */ + public function get_meta(): ?array + { + return $this->_meta; + } + + /** + * @return array<\WP\McpSchema\Common\Core\Icon>|null + */ + public function getIcons(): ?array + { + return $this->icons; + } +} diff --git a/src/Server/Tools/ToolAnnotations.php b/src/Server/Tools/ToolAnnotations.php new file mode 100644 index 0000000..e3c82eb --- /dev/null +++ b/src/Server/Tools/ToolAnnotations.php @@ -0,0 +1,204 @@ +title = $title; + $this->readOnlyHint = $readOnlyHint; + $this->destructiveHint = $destructiveHint; + $this->idempotentHint = $idempotentHint; + $this->openWorldHint = $openWorldHint; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * title?: string|null, + * readOnlyHint?: bool|null, + * destructiveHint?: bool|null, + * idempotentHint?: bool|null, + * openWorldHint?: bool|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['title'] ?? null), + self::asBoolOrNull($data['readOnlyHint'] ?? null), + self::asBoolOrNull($data['destructiveHint'] ?? null), + self::asBoolOrNull($data['idempotentHint'] ?? null), + self::asBoolOrNull($data['openWorldHint'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->title !== null) { + $result['title'] = $this->title; + } + if ($this->readOnlyHint !== null) { + $result['readOnlyHint'] = $this->readOnlyHint; + } + if ($this->destructiveHint !== null) { + $result['destructiveHint'] = $this->destructiveHint; + } + if ($this->idempotentHint !== null) { + $result['idempotentHint'] = $this->idempotentHint; + } + if ($this->openWorldHint !== null) { + $result['openWorldHint'] = $this->openWorldHint; + } + + return $result; + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @return bool|null + */ + public function getReadOnlyHint(): ?bool + { + return $this->readOnlyHint; + } + + /** + * @return bool|null + */ + public function getDestructiveHint(): ?bool + { + return $this->destructiveHint; + } + + /** + * @return bool|null + */ + public function getIdempotentHint(): ?bool + { + return $this->idempotentHint; + } + + /** + * @return bool|null + */ + public function getOpenWorldHint(): ?bool + { + return $this->openWorldHint; + } +} diff --git a/src/Server/Tools/ToolExecution.php b/src/Server/Tools/ToolExecution.php new file mode 100644 index 0000000..805041d --- /dev/null +++ b/src/Server/Tools/ToolExecution.php @@ -0,0 +1,93 @@ +taskSupport = $taskSupport; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * taskSupport?: 'forbidden'|'optional'|'required'|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** @var 'forbidden'|'optional'|'required'|null $taskSupport */ + $taskSupport = isset($data['taskSupport']) + ? self::asStringOrNull($data['taskSupport']) + : null; + + return new self( + $taskSupport + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->taskSupport !== null) { + $result['taskSupport'] = $this->taskSupport; + } + + return $result; + } + + /** + * @return 'forbidden'|'optional'|'required'|null + */ + public function getTaskSupport(): ?string + { + return $this->taskSupport; + } +} diff --git a/src/Server/Tools/ToolInputSchema.php b/src/Server/Tools/ToolInputSchema.php new file mode 100644 index 0000000..55d8ccd --- /dev/null +++ b/src/Server/Tools/ToolInputSchema.php @@ -0,0 +1,134 @@ +|null + */ + protected ?array $properties; + + /** + * @var array|null + */ + protected ?array $required; + + /** + * @param string|null $schema + * @param array|null $properties + * @param array|null $required + */ + public function __construct( + ?string $schema = null, + ?array $properties = null, + ?array $required = null + ) { + $this->type = self::TYPE; + $this->schema = $schema; + $this->properties = $properties; + $this->required = $required; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * '$schema'?: string|null, + * type: 'object', + * properties?: array|null, + * required?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['$schema'] ?? null), + self::asObjectMapOrNull($data['properties'] ?? null), + self::asStringArrayOrNull($data['required'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->schema !== null) { + $result['$schema'] = $this->schema; + } + $result['type'] = $this->type; + if ($this->properties !== null) { + $result['properties'] = $this->properties; + } + if ($this->required !== null) { + $result['required'] = $this->required; + } + + return $result; + } + + /** + * @return string|null + */ + public function getSchema(): ?string + { + return $this->schema; + } + + /** + * @return 'object' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return array|null + */ + public function getProperties(): ?array + { + return $this->properties; + } + + /** + * @return array|null + */ + public function getRequired(): ?array + { + return $this->required; + } +} diff --git a/src/Server/Tools/ToolListChangedNotification.php b/src/Server/Tools/ToolListChangedNotification.php new file mode 100644 index 0000000..30b9972 --- /dev/null +++ b/src/Server/Tools/ToolListChangedNotification.php @@ -0,0 +1,102 @@ +typedParams = $params; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * jsonrpc: '2.0', + * method: 'notifications/tools/list_changed', + * params?: array|\WP\McpSchema\Common\JsonRpc\NotificationParams|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + self::assertRequired($data, ['jsonrpc']); + + /** @var '2.0' $jsonrpc */ + $jsonrpc = self::asString($data['jsonrpc']); + + /** @var \WP\McpSchema\Common\JsonRpc\NotificationParams|null $params */ + $params = isset($data['params']) + ? (is_array($data['params']) + ? NotificationParams::fromArray(self::asArray($data['params'])) + : $data['params']) + : null; + + return new self( + $jsonrpc, + $params + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = parent::toArray(); + + if ($this->typedParams !== null) { + $result['params'] = $this->typedParams->toArray(); + } + + return $result; + } + + /** + * @return \WP\McpSchema\Common\JsonRpc\NotificationParams|null + */ + public function getTypedParams(): ?NotificationParams + { + return $this->typedParams; + } +} diff --git a/src/Server/Tools/ToolOutputSchema.php b/src/Server/Tools/ToolOutputSchema.php new file mode 100644 index 0000000..407c50e --- /dev/null +++ b/src/Server/Tools/ToolOutputSchema.php @@ -0,0 +1,138 @@ +|null + */ + protected ?array $properties; + + /** + * @var array|null + */ + protected ?array $required; + + /** + * @param string|null $schema + * @param array|null $properties + * @param array|null $required + */ + public function __construct( + ?string $schema = null, + ?array $properties = null, + ?array $required = null + ) { + $this->type = self::TYPE; + $this->schema = $schema; + $this->properties = $properties; + $this->required = $required; + } + + /** + * Creates an instance from an array. + * + * @param array{ + * '$schema'?: string|null, + * type: 'object', + * properties?: array|null, + * required?: array|null + * } $data + * @phpstan-param array $data + * @return self + */ + public static function fromArray(array $data): self + { + return new self( + self::asStringOrNull($data['$schema'] ?? null), + self::asObjectMapOrNull($data['properties'] ?? null), + self::asStringArrayOrNull($data['required'] ?? null) + ); + } + + /** + * Converts the instance to an array. + * + * @return array + */ + public function toArray(): array + { + $result = []; + + if ($this->schema !== null) { + $result['$schema'] = $this->schema; + } + $result['type'] = $this->type; + if ($this->properties !== null) { + $result['properties'] = $this->properties; + } + if ($this->required !== null) { + $result['required'] = $this->required; + } + + return $result; + } + + /** + * @return string|null + */ + public function getSchema(): ?string + { + return $this->schema; + } + + /** + * @return 'object' + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return array|null + */ + public function getProperties(): ?array + { + return $this->properties; + } + + /** + * @return array|null + */ + public function getRequired(): ?array + { + return $this->required; + } +}