Skip to main content
Glama
rajanmishra6

OpenSearch MCP Server

by rajanmishra6

OpenSearch MCP Server

A Model Context Protocol (MCP) server that lets AI assistants such as Claude, ChatGPT and Cursor search and inspect an OpenSearch cluster.

It runs in two modes:

  • Local (stdio): the AI app starts the server as a child process on your machine. No network exposure and no token needed.

  • Remote (Streamable HTTP): one shared server (production: https://opensearch-mcp.monotype.com) that clients reach over HTTPS. Every request must carry a valid API token or an OAuth token derived from one.

Tools

Area

Tool

What it does

Indices

list_indices

List all indices

get_mapping

Mapping of an index

get_settings

Settings of an index

Documents

search_documents

Search an index with Query DSL (default size 20)

Cluster

get_cluster_health

Cluster health

get_cluster_stats

Cluster-wide statistics

Dashboards

list_index_patterns

Index patterns stored in .kibana

generate_discover_url

Build an OpenSearch Dashboards Discover link for a query

Admin

get_ism_policies

Index State Management policies

get_index_templates

Index templates and their shard counts

get_shard_allocation

Shard distribution across nodes

get_hot_threads

Hot threads on all nodes

get_tasks

Running cluster tasks

get_recovery_status

Shard recovery progress and ETA

All tools are read-only.

Related MCP server: opensearch-mcp-server

Repository layout

This is the code repository. Deployment lives in a separate build repository (Helm chart, image Dockerfile, Jenkins pipeline). Jenkins checks out both and builds the image from this repository's src/.

src/opensearch_mcp_server/
  server.py            entry point (stdio, or Streamable HTTP with --transport streamable-http)
  es_client.py         OpenSearch client (reads OPENSEARCH_* variables)
  tools/               the MCP tools listed above
  auth/
    store.py           SQLite store for API tokens and OAuth grants (hashes only)
    provider.py        OAuth 2.1 authorization server backed by API tokens
    http_app.py        authenticated Streamable HTTP app (discovery, sign-in page, /mcp)
    cli.py             opensearch-mcp-token command
tests/                 pytest suite, including a fake OpenSearch server

The build repository's image replaces server.py with its own k8s/api_image/app/server.py and installs with its own pyproject.toml and constraints.txt. That version uses the official MCP SDK's FastMCP, serves the endpoint under a secret MCP_PATH_TOKEN prefix and adds a Host-header check. It still uses the tools/ and auth/ packages from this repository. When you change tools or authentication here, the next pipeline run picks them up. If you add a dependency, add it to the build repository's app/pyproject.toml and constraints.txt too.

Requirements

  • Python 3.10 or newer (the production image uses 3.11)

  • uv for local development

  • Network access to an OpenSearch cluster and credentials for it

git clone <code-repo-url> opensearch-mcp-server
cd opensearch-mcp-server
uv sync

Configuration

Settings come from environment variables. For local work, copy .env-template to .env (git-ignored); the server loads it automatically.

Variable

Default

Purpose

OPENSEARCH_HOST

OpenSearch URL, e.g. https://localhost:9200

OPENSEARCH_USERNAME, OPENSEARCH_PASSWORD

OpenSearch credentials (required)

DASHBOARDS_HOST

OpenSearch Dashboards URL, used by generate_discover_url

MCP_TRANSPORT

stdio

stdio or streamable-http (the --transport flag overrides it)

MCP_HOST / MCP_PORT

127.0.0.1 / 8000

Listen address for HTTP mode

MCP_PUBLIC_URL

http://<host>:<port>

Public origin, no path, e.g. https://opensearch-mcp.monotype.com. OAuth metadata, the resource URL and the 401 discovery header are built from it, never from request headers. Must be HTTPS except on localhost

MCP_HTTP_PATH

/mcp

Path of the MCP endpoint (the production image derives it from MCP_PATH_TOKEN instead)

MCP_AUTH_DB

~/.opensearch-mcp/auth.db

Token database. The server and opensearch-mcp-token must use the same file

MCP_ALLOWED_HOSTS

Comma-separated Host header values accepted on the MCP endpoint (DNS-rebinding protection)

MCP_FORWARDED_ALLOW_IPS

127.0.0.1

Proxies trusted for X-Forwarded-* (only affects logged client IPs)

MCP_OAUTH_ACCESS_TOKEN_TTL

3600

OAuth access token lifetime, seconds

MCP_OAUTH_REFRESH_TOKEN_TTL

2592000

OAuth refresh token lifetime, seconds (30 days)

MCP_OAUTH_REDIRECT_ALLOWLIST

Claude, ChatGPT, Cursor, loopback

Comma-separated redirect URIs clients may register; a trailing * is a prefix match, * alone allows any

MCP_STATELESS

false

Stateless Streamable HTTP (for several replicas without sticky sessions)

MCP_JSON_RESPONSE

false

Answer POSTs with plain JSON instead of an SSE stream

Local use with Claude Desktop (stdio)

Add this to claude_desktop_config.json, with --directory pointing at your clone:

{
  "mcpServers": {
    "opensearch": {
      "command": "uv",
      "args": ["--directory", "/path/to/opensearch-mcp-server", "run", "opensearch-mcp-server"],
      "env": {
        "OPENSEARCH_HOST": "https://localhost:9200",
        "OPENSEARCH_USERNAME": "",
        "OPENSEARCH_PASSWORD": "",
        "DASHBOARDS_HOST": "https://localhost:5601"
      }
    }
  }
}

The file is at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. Restart Claude Desktop, then ask things like "List all indices in the cluster" or "Show me the cluster health status".

Running the remote (HTTP) server locally

uv run opensearch-mcp-token create --name local-dev --expires 1d   # prints the token once
uv run opensearch-mcp-server --transport streamable-http            # http://127.0.0.1:8000/mcp

Then check it:

curl -i -X POST http://127.0.0.1:8000/mcp                           # 401 with a WWW-Authenticate header
curl -i -X POST http://127.0.0.1:8000/mcp \
  -H "Authorization: Bearer osmcp_..." \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

MCP Inspector (npx @modelcontextprotocol/inspector) also works. Choose Streamable HTTP and add the Authorization header.

Authentication

How it works

  • API tokens are created by an operator with opensearch-mcp-token. They look like osmcp_<id>_<secret>. Clients that can send a fixed header use Authorization: Bearer osmcp_... directly.

  • OAuth is for clients that only support MCP OAuth, such as Claude custom connectors and ChatGPT. The client discovers this server's authorization server from the 401 response, registers itself (Dynamic Client Registration) and opens a sign-in page in the browser. There the user pastes an API token. The client then gets short-lived access tokens and rotating refresh tokens. These are tied to that API token: they never outlive it, and they stop working the moment it expires, is revoked or is rotated.

  • Requests without a valid token get 401 and never reach OpenSearch. Tool calls re-check the token right before running. A session opened with one token cannot be used with another.

  • Only SHA-256 hashes of tokens, authorization codes and client secrets are stored, in a SQLite file created with mode 0600. Tokens and the secret URL prefix are redacted from logs.

Managing tokens

opensearch-mcp-token create --name <label>            # asks for an expiration
opensearch-mcp-token list [--all]                     # id, name, status, created, expires, last used
opensearch-mcp-token rotate <id> [--grace 1h]         # new token (asks for its expiration); old one revoked
opensearch-mcp-token revoke <id>                      # immediate, including OAuth sessions created with it

Every create and rotate asks "When should this token expire?". Accepted answers:

  • a date, 2026-12-31, meaning the end of that day in UTC

  • a date and time, 2026-12-31T18:00 (UTC) or 2026-12-31T18:00+05:30

  • a duration from now: 30m, 12h, 90d, 8w

  • none for a token that never expires (you are asked to confirm)

Past or unreadable values are asked again. --expires <value> skips the prompt for scripts. The token is printed once on stdout (other messages go to stderr), so TOKEN=$(opensearch-mcp-token create --name ci --expires 30d) works. It cannot be shown again; create a new one if it is lost.

Changes apply on the next request, with no restart. Use one token per person or integration so you can revoke them separately. With rotate --grace 1h, the old token keeps working for an hour so clients can switch over.

Connecting clients

Use the endpoint URL https://opensearch-mcp.monotype.com/<MCP_PATH_TOKEN>/mcp in production, or http://127.0.0.1:8000/mcp locally. Ask the operator for the URL and an API token.

Claude (claude.ai, Desktop, mobile)

Claude custom connectors use OAuth by default. Sending a fixed Authorization header is a beta that only some organizations have.

OAuth (works on every plan):

  1. Open Settings → Connectors → Add custom connector. On Team/Enterprise, an Owner first adds it under Organization settings → Connectors → Add → Custom → Web.

  2. Enter a name and the endpoint URL. Leave the OAuth client ID and secret empty.

  3. Click Connect. A browser page from this server opens.

  4. Paste your API token and click Authorize. Claude lists the 14 tools.

When the API token expires or is revoked, Claude asks you to connect again. Paste a new token.

Request headers (beta organizations only): when adding the connector, choose no sign-in. Add the header Authorization with the value Bearer osmcp_.... Type the Bearer prefix yourself, because Claude sends the value exactly as entered.

Claude Code

claude mcp add --transport http opensearch https://opensearch-mcp.monotype.com/<MCP_PATH_TOKEN>/mcp \
  --header "Authorization: Bearer osmcp_..."

Or leave out --header and run /mcp in Claude Code to sign in through the browser.

Claude API (MCP connector)

Pass the endpoint URL as the MCP server url and the API token as authorization_token.

ChatGPT

Enable developer mode, add a custom MCP server with the endpoint URL and choose OAuth. Leave the client fields empty; ChatGPT registers itself, and you sign in with your API token.

Cursor, VS Code and other clients

Any client that can set headers works with the token directly, e.g. in Cursor's mcp.json:

{
  "mcpServers": {
    "opensearch": {
      "url": "https://opensearch-mcp.monotype.com/<MCP_PATH_TOKEN>/mcp",
      "headers": { "Authorization": "Bearer osmcp_..." }
    }
  }
}

Production deployment

Production runs on EKS behind an internet-facing AWS NLB (TLS terminated with ACM) and Cloudflare. Everything for it is in the build repository.

Build repo path

Purpose

k8s/api_image/

Image Dockerfile (Python 3.11, non-root, read-only root filesystem), app/server.py, app/pyproject.toml, constraints.txt, prebuildsteps.sh (build and push to ECR)

k8s/helm/

Chart: Deployment, Service, NLB, ConfigMap, Secret, PersistentVolumeClaim for the token database

k8s/helm/prod/

prod.yaml values and sops-encrypted secrets.prod.yaml (OPENSEARCH_PASSWORD, MCP_PATH_TOKEN)

Makefile

build_api_image, deploy_image_to_prod_k8, lint, lb_info, token_* targets

push_to_env/prod.groovy

Jenkins pipeline: check out both repos, build, deploy with helm secrets, record the version

Pipeline

Jenkins checks out this repository (the "Codeget" stage) and the build repository. prebuildsteps.sh moves k8s/api_image/* to the workspace root and builds and pushes <ECR>/opensearch-mcp:<build>. Then make deploy_image_to_prod_k8 runs helm secrets upgrade --install --atomic. A failed rollout is rolled back automatically.

Before the first run: the "Codeget" stage in push_to_env/prod.groovy must point at this code repository's URL and branch.

What the chart sets up

  • MCP_TRANSPORT=streamable-http. MCP_PUBLIC_URL and MCP_ALLOWED_HOSTS come from INGRESS_HOST (opensearch-mcp.monotype.com).

  • MCP_PATH_TOKEN (sops secret) is a secret URL prefix on top of token authentication. It is not a replacement for it.

  • A 1Gi ReadWriteOnce volume, opensearch-mcp-data, is mounted at /data with MCP_AUTH_DB=/data/auth.db. It is annotated helm.sh/resource-policy: keep, so uninstalling the release does not delete the tokens. It uses the cluster's default StorageClass; set persistence.storageClassName (e.g. gp3) in prod.yaml if there is none. Check with kubectl get storageclass. To reuse a restored volume, set persistence.existingClaim.

  • Strategy Recreate: only one pod can mount the volume, so the old pod stops before the new one starts. Expect about a minute of downtime per deploy. The chart refuses to render with a rolling update while persistence is on.

  • One replica. Sessions are held in memory and the volume is single-attach.

  • The NLB, the kubelet probes and helm test use /health, which needs no token.

First deploy and token management

After the first deploy there are no tokens, so every MCP request is rejected. From the build repository, with the production kube context selected:

make token_create NAME=rajan-claude                  # interactive: asks for the expiration
make token_create NAME=ci-bot EXPIRES=90d            # non-interactive
make token_list [ALL=1]
make token_rotate ID=<id> [GRACE=1h] [EXPIRES=...]
make token_revoke ID=<id>

These run opensearch-mcp-token inside the running pod (kubectl -n opensearch exec -it deploy/opensearch-mcp -- opensearch-mcp-token ...).

Network and proxy requirements

  • All of these paths must reach the pod: /<MCP_PATH_TOKEN>/mcp, /.well-known/oauth-protected-resource/<MCP_PATH_TOKEN>/mcp, /.well-known/oauth-authorization-server, /authorize, /token, /register, /revoke, /oauth/login, /health. The NLB forwards everything.

  • Cloudflare: Bot Fight Mode, WAF challenges or Cloudflare Access on these paths will break Claude and ChatGPT. Claude's traffic comes from 160.79.104.0/21. Rate limiting on /oauth/login, /register and /token is recommended.

  • The Authorization header must be passed through. Responses must not be buffered, and OAuth endpoints must answer within Claude's 10 second limit.

Backups

The token database is the only state. Back it up with kubectl -n opensearch exec deploy/opensearch-mcp -- python -c "import sqlite3; s=sqlite3.connect('/data/auth.db'); d=sqlite3.connect('/tmp/backup.db'); s.backup(d)" followed by kubectl cp, or with EBS snapshots. It contains only hashes; if it is lost, issue new tokens.

Testing

uv sync
uv run pytest

The suite starts the real server against a fake OpenSearch (tests/fake_opensearch.py), so no cluster is needed. It covers:

  • MCP initialization, tool discovery and tool calls

  • valid, invalid, expired, non-expiring, revoked and rotated tokens, including revocation in the middle of a session

  • the full Claude OAuth flow, from the 401 to discovery, registration, PKCE sign-in, code exchange, refresh rotation and revocation

  • the stdio transport

  • the token CLI prompts

tests/test_deploy_image.py covers the production image: secret prefix, Host-header check and log redaction. It is skipped unless the installed package is the image build. To run it, copy src/ plus the build repository's app/server.py, app/pyproject.toml and constraints.txt into a directory as the Dockerfile does, pip install --constraint constraints.txt . into a Python 3.11 virtualenv, then run python -m pytest from this repository with that interpreter.

Security notes and limitations

  • The sign-in page asks for the API token itself. Users should only paste it into a sign-in they started from their own AI client. The page shows where it will redirect, and redirect URIs are limited by MCP_OAUTH_REDIRECT_ALLOWLIST.

  • Everyone who uses the same token shares one identity, and every token can use every tool. There are no per-tool scopes.

  • OpenSearch is accessed with a single service account (OPENSEARCH_USERNAME), whatever the MCP caller. The client is created with certificate verification disabled (verify_certs=False).

  • Client ID Metadata Documents are not supported, so Claude and ChatGPT use Dynamic Client Registration, which creates one client record per connection.

  • Authentication covers Streamable HTTP only. The old SSE transport is not available in HTTP mode. stdio stays local and unauthenticated by design.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to interact with OpenSearch clusters to monitor cluster health, manage indices, and perform data searches. It provides a standardized interface for real-time OpenSearch operations within MCP-compatible environments like Open WebUI.
    -
  • A
    license
    B
    quality
    F
    maintenance
    Enables interaction with OpenSearch clusters, providing tools to list indices, retrieve mappings, search using query DSL, and get shard information.
    4
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language querying and analysis of OpenTelemetry traces, metrics, and logs stored in Elasticsearch/OpenSearch, allowing AI assistants to investigate performance issues, find root causes, and explore system behavior.
    7 npm
    14
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Read-only MCP server for exploring and searching OpenSearch clusters, enabling log analysis, index exploration, and query execution.
    8
    MIT