Guide · OpenSearch · Serverless

Amazon OpenSearch Serverless for MCP Tools

Amazon OpenSearch Serverless (AOSS) removes cluster provisioning and scaling from the operational picture — you create a collection, apply a three-layer access policy, and write documents. Three problems catch developers building MCP backends on AOSS: the SigV4 signing service name is 'aoss', not 'es' — using 'es' produces HTTP 403 with no explanation; OpenSearch Serverless does not support ISM policies, scroll API, cat APIs, or cluster-level admin operations — any MCP tool that relied on these for managed OpenSearch will break silently on Serverless; and the minimum capacity is 2 indexing OCUs + 2 search OCUs per collection even when idle, costing approximately $0.48/hour (~$350/month) regardless of traffic — Serverless is only cheaper than a managed domain above moderate traffic levels.

TL;DR

Choose collection type first: search for full-text + analytics, timeseries for high-ingest write-heavy workloads, vectorsearch for k-NN. Apply policies in order: encryption → network → data access. Use service: 'aoss' in SigV4 signer. Minimum cost: 2 indexing + 2 search OCUs at $0.24/OCU-hour = ~$0.48/hr. Scale up to 200 OCUs automatically. No ISM, scroll, or admin APIs.

Collection types: search vs timeseries vs vectorsearch

The collection type is immutable after creation and determines the underlying storage engine, supported APIs, and auto-scaling behaviour:

TypeStorageSupported APIsBest for
searchHot SSD (low-latency reads)Full OpenSearch query DSL, aggregations, PPL, SQLUser-facing search, analytics dashboards, log exploration
timeseriesCost-optimised tiered (S3-backed)Ingest + time-range queries; no full-text searchHigh-volume ingest (metrics, traces, logs) — writes optimised
vectorsearchHot SSD with k-NN graphk-NN queries, full-text via BM25, hybrid searchSemantic search, RAG pipelines, MCP tool discovery

For AliveMCP's semantic endpoint similarity search (find similar live servers when a given server goes down), the vectorsearch collection type is the correct choice. For ingesting raw probe results for analytics, timeseries cuts storage cost by ~60% versus search at the expense of full-text query capability on the probe data.

Three-layer access policy hierarchy

Every AOSS collection requires three distinct policy types, applied in dependency order. Missing any one prevents the collection from being accessible:

import { OpenSearchServerlessClient, CreateSecurityPolicyCommand,
  CreateAccessPolicyCommand } from '@aws-sdk/client-opensearchserverless';

const aossClient = new OpenSearchServerlessClient({ region: 'us-east-1' });

// Layer 1: Encryption policy — KMS key assignment
// (AWS-managed key is fine unless you need BYOK)
await aossClient.send(new CreateSecurityPolicyCommand({
  name: 'mcp-endpoints-encryption',
  type: 'encryption',
  policy: JSON.stringify({
    Rules: [
      { ResourceType: 'collection', Resource: ['collection/mcp-endpoints'] },
    ],
    AWSOwnedKey: true,  // Use AWS-managed key; set false + KMSMasterKeyId for BYOK
  }),
}));

// Layer 2: Network policy — controls whether collection is accessible from public internet or VPC
await aossClient.send(new CreateSecurityPolicyCommand({
  name: 'mcp-endpoints-network',
  type: 'network',
  policy: JSON.stringify([
    {
      Rules: [
        { ResourceType: 'collection', Resource: ['collection/mcp-endpoints'] },
        { ResourceType: 'dashboard', Resource: ['collection/mcp-endpoints'] },
      ],
      AllowFromPublic: true,  // false + SourceVPCEs for VPC-only access
      // SourceVPCEs: ['vpce-xxxxxxxxxxxxxxxxx'],  // VPC endpoint ID if AllowFromPublic: false
    },
  ]),
}));

// Layer 3: Data access policy — IAM principals + allowed operations
await aossClient.send(new CreateAccessPolicyCommand({
  name: 'mcp-endpoints-data-access',
  type: 'data',
  policy: JSON.stringify([
    {
      Rules: [
        {
          ResourceType: 'collection',
          Resource: ['collection/mcp-endpoints'],
          Permission: [
            'aoss:CreateCollectionItems',
            'aoss:UpdateCollectionItems',
            'aoss:DescribeCollectionItems',
          ],
        },
        {
          ResourceType: 'index',
          Resource: ['index/mcp-endpoints/*'],  // All indices in this collection
          Permission: [
            'aoss:CreateIndex',
            'aoss:UpdateIndex',
            'aoss:DescribeIndex',
            'aoss:ReadDocument',
            'aoss:WriteDocument',
          ],
        },
      ],
      Principal: [
        'arn:aws:iam::123456789012:role/McpProbeWorkerRole',
        'arn:aws:iam::123456789012:role/McpDashboardReaderRole',
      ],
    },
  ]),
}));

The data access policy uses aoss: action prefixes, not es: — a common mistake when copying patterns from managed OpenSearch IAM policies. The aoss:WriteDocument permission is required for both bulk indexing and single-document indexing operations.

SigV4 client configuration for Serverless

The service name in the SigV4 signer differentiates AOSS from managed OpenSearch. Every request signed with service: 'es' against an AOSS endpoint returns HTTP 403 with no body — the AWS signing infrastructure rejects it before the request reaches the collection.

import { Client } from '@opensearch-project/opensearch';
import { AwsSigv4Signer } from '@opensearch-project/opensearch/aws';
import { defaultProvider } from '@aws-sdk/credential-provider-node';

// OpenSearch Serverless client — key difference: service is 'aoss', not 'es'
const serverlessClient = new Client({
  ...AwsSigv4Signer({
    region: process.env.AWS_REGION ?? 'us-east-1',
    service: 'aoss',          // 'aoss' for Serverless; 'es' for managed domain
    getCredentials: () => defaultProvider()(),
  }),
  node: `https://${process.env.AOSS_COLLECTION_ENDPOINT}`,
  // No auth field — SigV4 handles authentication
  // Serverless endpoint format: {collection-id}.{region}.aoss.amazonaws.com
});

// Managed OpenSearch Service client — service is 'es'
const managedClient = new Client({
  ...AwsSigv4Signer({
    region: process.env.AWS_REGION ?? 'us-east-1',
    service: 'es',            // 'es' for managed domain
    getCredentials: () => defaultProvider()(),
  }),
  node: `https://${process.env.OPENSEARCH_DOMAIN_ENDPOINT}`,
});

OCU capacity model and cost thresholds

OpenSearch Serverless charges per OpenSearch Compute Unit (OCU) per hour, billed separately for indexing capacity and search capacity. One OCU provides approximately 6 GB of RAM for the respective workload. Capacity auto-scales between a minimum of 2 OCUs and a maximum of 200 OCUs per dimension.

ScenarioIndexing OCUsSearch OCUsHourly costMonthly cost
Idle (minimum)22$0.48~$350
Light ingest + queries24$1.44~$1,040
Moderate production46$2.40~$1,730
High-throughput810$4.32~$3,115

For AliveMCP writing ~83 probe results/second, a t3.medium.search managed domain (2 OCUs equivalent, ~$60/month) is significantly cheaper than the AOSS minimum ($350/month). AOSS becomes cost-competitive only when the managed domain's instance scaling (to handle peak query bursts) would push the managed domain cost above $350/month — roughly at 50,000+ concurrent queries per hour with unpredictable burst patterns.

AOSS limitations vs managed OpenSearch

These capabilities exist in managed Amazon OpenSearch Service but are unavailable in OpenSearch Serverless. MCP tools relying on any of these must use a managed domain instead:

CapabilityManaged domainServerless
Index State Management (ISM)✅ Full support❌ Not available
Scroll API✅❌ Not available — use search_after instead
Cat APIs (/_cat/*)✅❌ Not available
Cluster admin APIs✅❌ Not available
Anomaly detection plugin✅❌ Not available
Alerting plugin✅❌ Not available
k-NN search✅ (all engines)✅ vectorsearch collection only
SQL/PPL queries✅✅ search collection only
OpenSearch Dashboards✅✅ (subset of features)
UltraWarm / Cold storage✅❌ Storage is managed automatically

VPC endpoint for private AOSS access

When the network policy sets AllowFromPublic: false, all client requests must come through an AOSS VPC endpoint (not a regular VPC endpoint for OpenSearch Service — a separate resource type is required). Create an AOSS VPC endpoint via the AWS console or CLI and reference its ID in the network policy's SourceVPCEs array.

// Create AOSS VPC endpoint (separate from regular OpenSearch VPC endpoint)
aws opensearchserverless create-vpc-endpoint \
  --name mcp-probe-worker-aoss-endpoint \
  --vpc-id vpc-xxxxxxxxxxxxxxxxx \
  --subnet-ids subnet-xxxxxxxxxxxxxxxxx subnet-yyyyyyyyyyyyyyyyy \
  --security-group-ids sg-xxxxxxxxxxxxxxxxx

# Update network policy to restrict to VPC endpoint only
aws opensearchserverless update-security-policy \
  --name mcp-endpoints-network \
  --type network \
  --policy '[{"Rules":[{"ResourceType":"collection","Resource":["collection/mcp-endpoints"]}],"AllowFromPublic":false,"SourceVPCEs":["vpce-xxxxxxxxxxxxxxxxx"]}]'

VPC endpoint creation takes 5–10 minutes. Until it's active, all requests return HTTP 403. The VPC endpoint costs $0.01/hour per AZ (standard AWS Interface Endpoint pricing) in addition to the OCU charges.

How AliveMCP evaluates AOSS for endpoint similarity search

AliveMCP uses a managed OpenSearch domain (not Serverless) for its primary probe-results index because ISM policies and the scroll API are both required for the 90-day retention and history-export pipelines. However, AOSS with a vectorsearch collection is used for the semantic endpoint similarity feature — the collection holds only 50,000 endpoint embeddings, rarely ingests new data, and only runs k-NN queries. At this scale, the minimum 2+2 OCU floor makes AOSS more expensive than a t3.small.search managed domain would be for the same workload, so the vectorsearch collection is currently under cost review.

Related guides