Skip to content

Conversation

@pushpak1300
Copy link
Member

@pushpak1300 pushpak1300 commented Dec 5, 2025

Resolves #86

This PR adds support for the MCP completion utility, allowing servers to provide IDE-like autocompletion suggestions for Prompt arguments and Resource template variables.

Completions are opt-in. MCP servers will not enable this by default. To turn it on, add the capability in your server class:

protected function boot(): void
{
    $this->addCapability(self::CAPABILITY_COMPLETION);
}

How It Works

  1. Opt-in per primitive: Prompts and Resources implement SupportsCompletion interface
  2. Context-aware: Access current argument values via $context array parameter
  3. Two factory methods: Choose between automatic filtering (::match()) or raw data (::result())
  4. Built-in helpers: Filter completions with CompletionHelper::filterByPrefix()

API Overview

CompletionResponse::match() - Automatic Filtering

Returns completions with automatic prefix filtering. Best for static arrays and enums.

// Array - returns ArrayCompletionResponse (with prefix filtering)
CompletionResponse::match(['php', 'python', 'javascript']);
// User types "py" → Returns: ['python']

// Enum class - returns EnumCompletionResponse (with prefix filtering)
CompletionResponse::match(StatusEnum::class);
// User types "act" → Returns: ['active']

// Empty result
CompletionResponse::empty();

CompletionResponse::result() - Raw Data

Returns data as-is without automatic filtering. Developer has full control.

// Array - returns raw data without filtering
CompletionResponse::result(['php', 'python', 'javascript']);
// User types "py" → Returns: ['php', 'python', 'javascript'] (all items)

// Single string - wraps in array
CompletionResponse::result('single-value');
// Returns: ['single-value']

When to Use Which

  • Use ::match() when you want automatic prefix-based filtering (most common)
  • Use ::result() when you want full control over filtering or need to return raw data

Usage Example

class TaskPrompt extends Prompt implements SupportsCompletion
{
    public function complete(string $argument, string $value, array $context): CompletionResponse
    {
        return match ($argument) {
            'assignee' => CompletionResponse::match(['alice', 'bob', 'charlie']),
            'priority' => CompletionResponse::match(Priority::class),
            'category' => CompletionResponse::result(['backend', 'frontend', 'infra']),
            'status' => $context['priority'] === 'high'
                ? CompletionResponse::result(['in-progress', 'blocked'])
                : CompletionResponse::result(['pending', 'in-progress', 'completed']),
            default => CompletionResponse::empty(),
        };
    }
}

Resource Template Completion

class UserFileResource extends Resource implements HasUriTemplate, SupportsCompletion
{
    public function uriTemplate(): UriTemplate
    {
        return new UriTemplate('file://users/{userId}/files/{fileId}');
    }

    public function complete(string $argument, string $value, array $context): CompletionResponse
    {
        return match ($argument) {
            'userId' => CompletionResponse::match(['user-1', 'user-2']),
            'fileId' => $context['userId']
                ? CompletionResponse::match($this->getFilesForUser($context['userId']))
                : CompletionResponse::empty(),
            default => CompletionResponse::empty(),
        };
    }
}

Context-Aware Completions

The $context array provides access to already-filled argument values, enabling dependent completions:

public function complete(string $argument, string $value, array $context): CompletionResponse
{
    return match ($argument) {
        'projectId' => CompletionResponse::match(['project-1', 'project-2', 'project-3']),
        'taskId' => $context['projectId'] ?? null
            ? CompletionResponse::result($this->getTasksForProject($context['projectId']))
            : CompletionResponse::empty(),
        default => CompletionResponse::empty(),
    };
}

Testing

Test completions using the fluent testing API:

MyServer::completion(LanguagePrompt::class, 'language', 'py')
    ->assertHasCompletions(['python'])
    ->assertCompletionCount(1);

MyServer::completion(TaskPrompt::class, 'status', '', ['priority' => 'high'])
    ->assertHasCompletions(['in-progress', 'blocked']);

// Test resource template completions with context
MyServer::completion(
    UserFileResource::class,
    argumentName: 'fileId',
    argumentValue: '',
    ['userId' => 'user-1']
)
    ->assertCompletionValues(['file1.txt', 'file2.txt'])
    ->assertCompletionCount(2);

@pushpak1300 pushpak1300 marked this pull request as ready for review December 8, 2025 17:17
@pushpak1300 pushpak1300 marked this pull request as draft December 11, 2025 17:19
@pushpak1300 pushpak1300 marked this pull request as ready for review December 11, 2025 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support resource/prompts completions

2 participants