Skip to main content
Glama

@pulumi/mcp-server

Official
by pulumi
pulumi-api-client.ts4.8 kB
/** * Pulumi API Client for resource search * This module handles actual API calls to Pulumi Cloud Service */ export interface PulumiSearchRequest { query: string; org: string; top?: number; properties?: boolean; } export interface PulumiSearchResponse { resources: { name: string; type: string; project: string; stack: string; properties: Record<string, unknown>; urn?: string; id?: string; created?: string; modified?: string; provider?: string; package?: string; }[]; totalResources: number; } export interface PulumiSearchApiClientConfig { apiUrl: string; accessToken: string; timeout?: number; } /** * Client for interacting with Pulumi Cloud Resource Search API */ export class PulumiSearchApiClient { constructor(private config: PulumiSearchApiClientConfig) {} /** * Search for resources using Pulumi Cloud Resource Search API * Calls: GET /api/orgs/{orgName}/search/resourcesv2 */ async searchResources(request: PulumiSearchRequest): Promise<PulumiSearchResponse> { // Set page size based on top parameter to avoid over-fetching const pageSize = request.top ? Math.min(20, request.top) : 20; const url = new URL(`/api/orgs/${request.org}/search/resourcesv2`, this.config.apiUrl); // Add query parameters url.searchParams.set('organization', request.org); url.searchParams.set('query', request.query); url.searchParams.set('properties', (request.properties || false).toString()); url.searchParams.set('size', pageSize.toString()); url.searchParams.set('page', '1'); // API uses 1-indexed pages // Initial fetch outside loop const response = await this.makeRequest(url.toString()); const data = await response.json(); let allResources: PulumiSearchResponse['resources'] = data.resources || []; const totalResources = data.total || 0; // The 250 constant is found empirically - with this limit, queries can complete successfully // while higher limits may cause timeouts or failures const maxResultsToFetch = Math.min(totalResources, request.top || 250); let nextUrl = data.pagination?.next; while (nextUrl && allResources.length < maxResultsToFetch) { // Use robust URL resolution to handle both absolute and relative URLs const fullUrl = new URL(nextUrl, this.config.apiUrl); const pageResponse = await this.makeRequest(fullUrl.toString()); const pageData = await pageResponse.json(); const pageResources = pageData.resources || []; // If we got no results, we're done if (pageResources.length === 0) { break; } allResources.push(...pageResources); nextUrl = pageData.pagination?.next; } // Trim results to respect the top parameter (capped to maxResultsToFetch) if (allResources.length > maxResultsToFetch) { allResources = allResources.slice(0, maxResultsToFetch); } return { resources: allResources, totalResources }; } private async makeRequest(url: string): Promise<Response> { const response = await fetch(url, { method: 'GET', headers: { Authorization: `token ${this.config.accessToken}`, 'Content-Type': 'application/json', 'User-Agent': 'pulumi-mcp-server' }, signal: AbortSignal.timeout(this.config.timeout || 30000) }); if (!response.ok) { let errorMessage = `Pulumi API error: ${response.status} ${response.statusText}`; try { const errorData = await response.json(); if (errorData.message) { errorMessage += ` - ${errorData.message}`; } } catch { // Ignore JSON parsing errors for error responses } if (response.status === 402) { throw new Error('Quota limit exceeded for resource search API'); } if (response.status === 401) { throw new Error('Unauthorized: Invalid or expired access token'); } if (response.status === 403) { throw new Error('Forbidden: Insufficient permissions for resource search'); } if (response.status === 404) { throw new Error('Organization not found or resource search endpoint not available'); } throw new Error(errorMessage); } return response; } } /** * Factory function to create PulumiApiClient with environment-based configuration */ export function createPulumiSearchApiClient(): PulumiSearchApiClient { const apiUrl = process.env.PULUMI_API_URL || 'https://api.pulumi.com'; const accessToken = process.env.PULUMI_ACCESS_TOKEN; if (!accessToken) { throw new Error('PULUMI_ACCESS_TOKEN environment variable is required'); } return new PulumiSearchApiClient({ apiUrl, accessToken, timeout: 30000 }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pulumi/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server