# reShapr > reShapr is a no-code AI translation layer that instantly converts existing APIs (REST, gRPC, GraphQL) into secure, production-ready MCP (Model Context Protocol) servers — without writing a line of code. This file contains all documentation content in a single document following the llmstxt.org standard. ## How It Works reShapr is a zero-code AI translation layer. Instead of building an MCP server from scratch, you can use reShapr to instantly translate your existing APIs (REST, gRPC, GraphQL) into AI-native endpoints. With reShapr, you can create secure MCP servers in seconds without coding by connecting the platform to your existing API artifacts. As of today, reShapr supports ingesting: - [OpenAPI 3.x specifications](https://spec.openapis.org/oas/latest.html) - [GraphQL schemas](https://spec.graphql.org/) - [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) / [Protocol Buffer definitions](https://protobuf.dev/programming-guides/proto3/) ## From API artifact to MCP endpoint An imported API artifact defines a versioned Service and its operations. A Configuration Plan then selects the operations and attached reShapr Artifacts to expose, identifies the backend, and applies endpoint policies. An Exposition assigns that Plan to a Gateway Group. Running proxies register logical Gateways in that group and serve the resulting MCP endpoint. ```mermaid flowchart LR Artifact[API Artifact] --> Service[Versioned Service] Service --> PlanA[Configuration Plan A] Service --> PlanB[Configuration Plan B] PlanA --> ExpositionA[Exposition] PlanB --> ExpositionB[Exposition] ExpositionA --> Group[Gateway Group] ExpositionB --> Group Group --> GatewayA[Gateway] Group --> GatewayB[Gateway] GatewayA --> ProxyA[reShapr proxy] GatewayB --> ProxyB[reShapr proxy] ProxyA --> Backend[Backend API] ProxyB --> Backend ``` One Service can therefore produce multiple MCP surfaces. For example, one Plan can expose a small read-only surface while another includes a business-oriented Custom Tool and an output filter. **Context Control** applies at two complementary points: - At configuration time, a Plan selects Service operations and attached Prompts, Resources, Custom Tools, and output filters. - At call time, the selected filters and output encoding can reduce or reshape a Tool response before it returns to the MCP client. Once reShapr discovers your services, you configure: - Security mechanisms - Exposition options (all operations, read-only operations, etc.) - Existing backend endpoint targets Then reShapr exposes your MCP server through the proxies whose registered Gateways are selected by its Exposition. :::info Core Architecture reShapr separates configuration management from MCP request execution so that control-plane and proxy placement can follow operational and network requirements. The platform has two major parts: - **Control plane**: centralizes exposition configuration and policies. - **Data plane**: proxies that expose MCP servers and route runtime traffic. ::: This architecture supports multiple deployment models: 1. **Local development**: control plane and proxy run in one temporary environment. 2. **Centralized**: control plane and proxies run in one managed environment. 3. **Hybrid or split**: control plane and proxies run in different trust domains. 4. **Self-hosted or on-premises**: the organization operates the complete platform and its dependencies. These names describe runtime topology, not commercial availability or service-level guarantees. See **[Deployment Models and Trust Boundaries](../explanations/deployment-models-trust-boundaries.md)** for the traffic flows and operational responsibilities of each model. See also: - **[Why reShapr?](./why-reshapr.md)** - **[Services and Artifacts](../explanations/services-and-artifacts.md)** - **[Configuration Plan and Exposition](../explanations/configuration-and-exposition.md)** - **[Security Capabilities and Limits](../explanations/security-model.md)** - **[Deploy a Hybrid reShapr Proxy](../how-to-guides/deploy-hybrid-gateway.md)** --- ## Why reShapr? Hey hey… wondering why we built reShapr? 🤔 :::info Check out our 👉 [**blog post**](/blog/why-reshapr) on that topic! ::: ***Building Your Own MCP Server Is a Trap!*** *At first glance, building an MCP Server may seem straightforward. But very quickly, most teams realize:* - **It’s more than OpenAPI**: You need translation layers, prompt guards, retries, rate limiting, and grounding logic. - **Security gets messy fast**: How do you avoid exposing credentials in LLM prompts? How do you enforce identity, scope, and input validation? - **Fragility creeps in**: Most DIY solutions end up as brittle pipelines of JSON transforms, hardcoded logic, and embedded hacks that fall apart under load or, worse, leak data. This approach often duplicates your API logic into a parallel, standalone MCP layer, creating unnecessary complexity, increased maintenance overhead, and misalignment between your API and its AI-facing interface. > **A smarter approach** is to **extend your existing API** infrastructure, not **reinvent it** ! **reShapr exists to solve this exact problem!** Rather than building an MCP server from scratch, you can use reShapr to **instantly translate your existing APIs (REST, gRPC, GraphQL) into AI-native endpoints.** - **Zero** code - **No** rewrites - **No** custom Agents - **No** vendor lock-in --- ## Context Control in Practice Start from the Open-Meteo Service created in the first endpoint tutorial. You will attach a declarative Custom Tool and an output filter, expose two Plans, and compare the same live weather call before and after filtering. :::tip Watch related demos See [Context Control applied to GitHub GraphQL](https://youtu.be/OjsSAt0JdOY) and [the resulting surface used by an MCP App](https://youtu.be/5ewU51_oM_8). The examples use a different API; the tutorial below remains the reproducible `1.0.0` procedure. ::: ## Prerequisites - The **[first MCP endpoint tutorial](./getting-started.md)** completed against reShapr `1.0.0` - reShapr CLI `1.0.0`, authenticated against that environment - `curl` and `jq` - A running proxy registered as a Gateway in Gateway Group `1` The example endpoint is intentionally unauthenticated. Add the authentication header required by your Exposition if you apply the procedure elsewhere. ## Locate the Open-Meteo Service Use structured CLI output to select the Service imported by the first tutorial: ```bash RESHAPR_SERVICE_ID="$( reshapr service list --output json \ | jq -er 'map(select(.name == "Open-Meteo Weather Forecast API" and .version == "1.0")) | first | .id' )" export RESHAPR_SERVICE_ID ``` ## Attach an agent-oriented Tool The sample **[Custom Tool definition](/examples/context-control/open-meteo-current-weather.yaml)** maps the canonical `GET /v1/forecast` operation, exposed by default as `get_v1_forecast`, to one action named `current_weather`. Its three inputs fix the requested weather fields while keeping the location explicit. Attach it and retain the Artifact name returned by reShapr: ```bash CUSTOM_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-current-weather.yaml' \ --output json \ | jq -er '.name' )" export CUSTOM_ARTIFACT_NAME ``` Inspect the capabilities derived during attachment: ```bash reshapr artifact list --serviceId "$RESHAPR_SERVICE_ID" ``` The `CAPS` count for this Artifact is `1`. Its capability is the `current_weather` Tool name; use `reshapr artifact get ` to display it. ## Attach an output filter The sample **[output filter](/examples/context-control/open-meteo-current-weather-filter.yaml)** retains the location, timezone, units, and current conditions while removing forecast metadata that this action does not need. ```bash FILTER_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-current-weather-filter.yaml' \ --output json \ | jq -er '.name' )" export FILTER_ARTIFACT_NAME ``` ## Create two bounded Plans Both Plans select only the canonical `GET /v1/forecast` operation. The Custom Tool replaces it with the agent-oriented `current_weather` Tool; the filtered Plan also selects its output filter. ```bash BASELINE_ARTIFACTS="$(jq -cn --arg custom "$CUSTOM_ARTIFACT_NAME" '[$custom]')" FILTERED_ARTIFACTS="$( jq -cn \ --arg custom "$CUSTOM_ARTIFACT_NAME" \ --arg filter "$FILTER_ARTIFACT_NAME" \ '[$custom, $filter]' )" BASELINE_PLAN_ID="$( reshapr config create 'weather-action-baseline' \ --serviceId "$RESHAPR_SERVICE_ID" \ --backendEndpoint 'https://api.open-meteo.com' \ --includedOperations '["GET /v1/forecast"]' \ --includedArtifacts "$BASELINE_ARTIFACTS" \ --output json \ | jq -er '.id' )" FILTERED_PLAN_ID="$( reshapr config create 'weather-action-filtered' \ --serviceId "$RESHAPR_SERVICE_ID" \ --backendEndpoint 'https://api.open-meteo.com' \ --includedOperations '["GET /v1/forecast"]' \ --includedArtifacts "$FILTERED_ARTIFACTS" \ --output json \ | jq -er '.id' )" export BASELINE_PLAN_ID FILTERED_PLAN_ID unset BASELINE_ARTIFACTS FILTERED_ARTIFACTS ``` Verify that the selections differ: ```bash reshapr config get "$BASELINE_PLAN_ID" reshapr config get "$FILTERED_PLAN_ID" ``` ## Expose both Plans Choose the scheme used by your Gateway, then create two named Expositions. Online uses `https`; the local Compose Gateway uses `http`. ```bash export MCP_SCHEME='https' BASELINE_MCP_URL="${MCP_SCHEME}://$( reshapr expo create \ --configuration "$BASELINE_PLAN_ID" \ --gateway-group 1 \ --name 'weather-action-baseline' \ --output json \ | jq -er '.endpoints[0]' )" FILTERED_MCP_URL="${MCP_SCHEME}://$( reshapr expo create \ --configuration "$FILTERED_PLAN_ID" \ --gateway-group 1 \ --name 'weather-action-filtered' \ --output json \ | jq -er '.endpoints[0]' )" export BASELINE_MCP_URL FILTERED_MCP_URL ``` ## Verify the Tool surface Use the same stateless MCP request for both endpoints: ```bash list_tools() { curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/list' \ --data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$1" | jq -r '.result.tools[].name' } list_tools "$BASELINE_MCP_URL" list_tools "$FILTERED_MCP_URL" ``` Each endpoint must list only `current_weather`. The generated `get_v1_forecast` operation is hidden behind the selected action. ## Call and measure both results Capture only the Tool content, outside the JSON-RPC envelope: ```bash call_weather() { curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header 'Mcp-Name: current_weather' \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"current_weather","arguments":{"latitude":"48.8566","longitude":"2.3522","timezone":"Europe/Paris"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$1" | jq -er '.result.content[0].text' } BASELINE_RESULT="$(call_weather "$BASELINE_MCP_URL")" FILTERED_RESULT="$(call_weather "$FILTERED_MCP_URL")" printf 'Baseline content: %s bytes\n' "$(printf '%s' "$BASELINE_RESULT" | wc -c | tr -d ' ')" printf 'Filtered content: %s bytes\n' "$(printf '%s' "$FILTERED_RESULT" | wc -c | tr -d ' ')" ``` Inspect the fields that remain: ```bash jq 'keys' <<<"$BASELINE_RESULT" jq 'keys' <<<"$FILTERED_RESULT" jq '.current' <<<"$FILTERED_RESULT" ``` The filtered result must contain only `latitude`, `longitude`, `timezone`, `current_units`, and `current`. Its byte count should be lower than the baseline for this request. The measurement uses UTF-8 bytes from the decoded Tool text, excludes the JSON-RPC envelope, and omits the trailing newline. Weather values and exact byte counts vary over time, so record the observed values rather than treating them as a product-wide reduction ratio. ## Enable TOON encoding Keep the JSON comparison above reproducible, then create a third Plan with a separate **[TOON output filter](/examples/context-control/open-meteo-current-weather-toon.yaml)**. It retains the same fields and applies `convertToToon` last. ```bash TOON_FILTER_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-current-weather-toon.yaml' \ --output json \ | jq -er '.name' )" TOON_ARTIFACTS="$( jq -cn \ --arg custom "$CUSTOM_ARTIFACT_NAME" \ --arg filter "$TOON_FILTER_NAME" \ '[$custom, $filter]' )" TOON_PLAN_ID="$( reshapr config create 'weather-action-toon' \ --serviceId "$RESHAPR_SERVICE_ID" \ --backendEndpoint 'https://api.open-meteo.com' \ --includedOperations '["GET /v1/forecast"]' \ --includedArtifacts "$TOON_ARTIFACTS" \ --output json \ | jq -er '.id' )" TOON_MCP_URL="${MCP_SCHEME}://$( reshapr expo create \ --configuration "$TOON_PLAN_ID" \ --gateway-group 1 \ --name 'weather-action-toon' \ --output json \ | jq -er '.endpoints[0]' )" export TOON_MCP_URL unset TOON_ARTIFACTS ``` Call the TOON Exposition with the same arguments and inspect the encoded Tool content: ```bash TOON_RESULT="$(call_weather "$TOON_MCP_URL")" printf 'TOON content: %s bytes\n' "$(printf '%s' "$TOON_RESULT" | wc -c | tr -d ' ')" printf '%s\n' "$TOON_RESULT" ``` The output is TOON text rather than JSON. This confirms the encoding stage is active; its byte count is one observation for this response, not a universal compression or token-saving claim. ## Result You replaced a broad generated operation with one agent-oriented Tool, selected different Artifact sets for the same Service, measured the effect of response filtering, and activated TOON encoding on a separate Exposition. ## Limits - This comparison covers one Open-Meteo request and does not predict token usage or model quality for other data. - Output filtering is not a security boundary. In reShapr `1.0.0`, a filter failure returns the original Tool response. - The before/after field comparison uses JSON before TOON is enabled so the retained keys remain directly inspectable. - Re-running the tutorial with the same Plan or Exposition names requires deleting or renaming the previous resources, including the TOON variants. ## Next step - **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)** for Prompts, Resources, Custom Tools, and filters. - **[Context Control: Mechanisms and Trade-offs](../explanations/context-control.md)** for choosing the appropriate control at each stage. - **[Tools Output Filtering](../references/spec-outtools-filtering.md)** for JSON Patch and TOON syntax. --- ## Your First GitOps-managed MCP Endpoint Install the reShapr controllers, commit a small set of Kubernetes resources, and let the operator create an MCP endpoint for the Open-Meteo API. ## Prerequisites - Kubernetes 1.25 or later and Helm 3.8 or later - `kubectl`, Helm, Git, `curl`, and `jq` - A running reShapr `1.0.0` **[control plane](../how-to-guides/deploy-kubernetes-production.md#3-configure-the-control-plane)** and **[proxy](../how-to-guides/deploy-kubernetes-production.md#6-configure-the-proxy)** reachable from the cluster - The reShapr `1.0.0` CLI authenticated with administrative access to that control plane - A proxy registered in the control plane's `Default Gateway Group` - A Git repository whose manifests are applied to the cluster, either manually or by your GitOps controller This tutorial uses reShapr controllers `0.0.3` through Helm chart `0.0.14`. The [controllers chart documentation](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/controllers/README.md) owns the complete values contract. ## 1. Register the operator identity The operator authenticates with a [projected Kubernetes ServiceAccount token](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). Register its subject with the control plane before installing the chart. Replace the server and organization settings with values for your environment: ```bash export RESHAPR_ADMIN_API_KEY='' reshapr admin --server https://reshapr.example.com \ service-account create reshapr-system-operator \ --k8s-subject reshapr-system:reshapr-controllers-operator \ --allowed-organizations '["reshapr"]' \ --validity-days 90 ``` The subject contains the namespace and ServiceAccount created by the Helm release. The [instance connection flow](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/instance-connection.md) describes the token exchange and required annotations. ## 2. Install the controllers Chart `0.0.14` packages the controllers `0.0.3` CRDs. Its default image tag remains `nightly`, so pin the reviewed operator image explicitly: ```bash helm install reshapr-controllers \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-controllers \ --version 0.0.14 \ --namespace reshapr-system \ --create-namespace \ --set operator.image.tag=0.0.3 \ --set admissionController.enabled=false ``` Wait for the operator and confirm that the custom-resource definitions are installed: ```bash kubectl rollout status deployment/reshapr-controllers-operator \ --namespace reshapr-system kubectl get crd | grep 'reshapr.io' ``` The admission controller is enabled by default and requires a serving certificate. This tutorial disables it because operator reconciliation does not use sidecar injection. ## 3. Add the endpoint resources to Git Create a directory for one environment: ```bash mkdir -p environments/dev/open-meteo ``` Add the three released sample manifests in dependency order: ```bash curl --fail --location \ https://raw.githubusercontent.com/reshaprio/reshapr-controllers/0.0.3/deploy/samples/open-meteo-api-service.yaml \ --output environments/dev/open-meteo/10-service.yaml curl --fail --location \ https://raw.githubusercontent.com/reshaprio/reshapr-controllers/0.0.3/deploy/samples/open-meteo-gitops-configurationplan.yaml \ --output environments/dev/open-meteo/20-configuration-plan.yaml curl --fail --location \ https://raw.githubusercontent.com/reshaprio/reshapr-controllers/0.0.3/deploy/samples/open-meteo-gitops-exposition.yaml \ --output environments/dev/open-meteo/30-exposition.yaml ``` The first file is a [`Service` custom resource](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/service-cr.md): ```yaml apiVersion: reshapr.io/v1alpha1 kind: Service metadata: name: open-meteo-api annotations: reshapr.io/instance: reshapr-control-plane-ctrl.reshapr-system reshapr.io/organization: reshapr spec: url: https://raw.githubusercontent.com/open-meteo/open-meteo/refs/heads/main/openapi/forecast.yml ``` Instead of imperatively importing this API contract through the reShapr API or CLI, the CR records the same desired outcome in Kubernetes. `apiVersion` and `kind` select the operator contract, the annotations select the target reShapr instance and organization, and `spec.url` identifies the API contract to import. The operator observes this desired state, calls the control-plane API, and reports the result in the CR's `status`. This declarative model applies to the capabilities represented by the [current reShapr CRDs](../references/kubernetes-apis.md). It does not imply that every API operation or CLI command has a Custom Resource equivalent. Each resource carries two annotations that select the control-plane instance and organization. Review them before committing: ```bash grep -R 'reshapr.io/instance\|reshapr.io/organization' environments/dev/open-meteo ``` If your control-plane Service or organization differs from the sample, update both annotations consistently. The released Exposition sample selects the control plane's `Default Gateway Group`. Commit the desired state: ```bash git add environments/dev/open-meteo git commit -m 'Add Open-Meteo MCP endpoint' git push ``` ## 4. Reconcile the resources If a GitOps controller watches this repository, wait for its next reconciliation. For a first local pass without one, apply the same committed directory directly: ```bash kubectl apply --filename environments/dev/open-meteo ``` Watch the operator-owned status fields: ```bash kubectl get services.reshapr.io,configurationplans.reshapr.io,expositions.reshapr.io \ --namespace default \ --watch ``` All three resources should reach `READY`. The Exposition status must also report the current Kubernetes generation: ```bash kubectl get exposition open-meteo-gitops-exposition \ --namespace default \ --output jsonpath='{.status.status}{" generation="}{.metadata.generation}{" observed="}{.status.observedGeneration}{"\n"}' ``` If a resource reports `ERROR`, inspect its status message and the operator logs: ```bash kubectl get exposition open-meteo-gitops-exposition --namespace default --output yaml kubectl logs --namespace reshapr-system \ --selector app.kubernetes.io/component=operator ``` ## 5. Call the MCP endpoint Use the MCP URL exposed by the proxy for the Open-Meteo Service. For a standalone proxy without ingress, first forward its Service: ```bash kubectl port-forward --namespace reshapr-proxies service/reshapr-proxy 7777:7777 ``` In another terminal, read the resolved Exposition ID from its status and build the endpoint URL: ```bash export EXPOSITION_ID="$(kubectl get exposition open-meteo-gitops-exposition \ --namespace default \ --output jsonpath='{.status.expositionId}')" export MCP_URL="http://localhost:7777/mcp/${EXPOSITION_ID}" ``` Discover the server and verify that it exposes MCP capabilities: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-gitops-tutorial","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result | {supportedVersions, capabilities}' ``` ## Result The three Kubernetes resources are committed as desired state, their status is `READY`, the Exposition has observed its current generation, and `server/discover` returns the MCP capabilities exposed by your proxy. ## Limits - Applying the directory with `kubectl` demonstrates the same declarative resources but does not install or configure a GitOps controller. - The released Service sample imports the Open-Meteo contract from its upstream default branch, so a later reconciliation can observe upstream contract changes. - A `READY` Exposition confirms control-plane reconciliation; the final MCP call also depends on a healthy proxy registered in the selected Gateway Group. - The default admission webhook is fail-open and is not required for operator reconciliation. - Availability, TLS, secret management, and database durability remain responsibilities of the deployed platform. ## Next step Use **[Manage reShapr Resources with GitOps](../how-to-guides/manage-resources-with-gitops.md)** to organize updates, Secrets, and cleanup for more endpoints. Then use **[Helm Charts Overview](../references/helm-charts.md)** to choose a production topology. --- ## Your First MCP Endpoint, End to End Import an OpenAPI contract, create a Configuration Plan and Exposition, then call a generated Tool and observe live weather data returned through reShapr. :::tip Watch the journey The [43-second Open-Meteo demo](https://youtu.be/EmBNZfUceTI) previews this workflow. Use the steps below for the current, verified procedure. ::: ## Prerequisites - Node.js 20 or later - `curl` and `jq` - Access to the **[reShapr Online Try](./try-reshapr-online.md)** or a **[local reShapr 1.0.0 environment](../how-to-guides/docker-compose.md)** - Outbound access to GitHub and `https://api.open-meteo.com` ## Installation The `reshapr` CLI is an NPM package available at **[https://www.npmjs.com/package/@reshapr/reshapr-cli](https://www.npmjs.com/package/@reshapr/reshapr-cli)**. It requires Node.js 20 or later and can be installed globally on Linux or macOS. We recommend installing the CLI with anonymous usage telemetry enabled: ```bash npm install -g @reshapr/reshapr-cli --allow-scripts=@scarf/scarf ``` The `--allow-scripts=@scarf/scarf` option explicitly authorizes Scarf's installation telemetry script. These anonymous metrics help the community measure adoption and support the project's future donation to the **[Agentic AI Foundation (AAIF)](https://aaif.io/)**. Telemetry is optional. **[Learn why we collect these metrics](https://github.com/reshaprio/reshapr.io/issues/27)**. To install the CLI without enabling telemetry, use: ```bash npm install -g @reshapr/reshapr-cli ``` The CLI remains fully functional when telemetry is not enabled. To remember your approval for future global installations, you can optionally configure npm at the user level: ```bash npm config set allow-scripts=@scarf/scarf --location=user ``` Check that everything is correctly installed: ```bash reshapr --version ``` Then inspect the current command index: ```bash reshapr --help ``` The command list evolves with the CLI. Use the embedded help as the source of truth and the **[CLI reference](../references/cli-commands.md)** for command details. ## Login to reShapr While we use the **[reShapr Online Try](./try-reshapr-online.md)** in this tutorial, you should point the server URL to your own environment if you have one set up. Common URLs include `http://localhost:5555` for Docker Compose or your cluster’s ingress URL for Kubernetes. Use the following `login` command with the `-s` option (or `--server`) to specify you’re joining the beta platform: ```bash reshapr login -s https://try.reshapr.io ``` Once this output appears, the system will wait for you to complete the authentication process: ```bash ❯ reshapr login -s https://try.reshapr.io ℹ️ Opening browser: https://try.reshapr.io/cli/login?redirect_uri=http://localhost:5556 ℹ️ Listening for authentication callback on http://localhost:5556 ✅ Login successful! ℹ️ Welcome, yada! ℹ️ Organization: yada ✅ Configuration saved to /Users/yacine/.reshapr/config ``` :::info If you're using your own setup, you can authenticate non-interactively with the `--username` and `--password` flags. ::: You’ll see that your connection information and ephemeral token are stored under your home folder. `reshapr logout` allows you to clean up everything. Once connected, you can check the platform information: ```bash reshapr info ``` Example output (user, organization, paths, and URLs vary): ```bash ❯ reshapr info ℹ️ User Information User : yada Organization: yada Server : https://app.try.reshapr.io ℹ️ Server Information Version : 1.0.0 Build time : Mode : on-premises Internal IDP: undefined ``` :::info **From this step, you have two choices**: exploring the detailed concepts step-by-step and executing detailed commands **OR** going directly to the **[All-in-one magic command 🪄](#all-in-one-magic-command-)** ::: ## Import Artifact & Service Importing an artifact is the first step to exposing MCP endpoints for your API. Artifacts enable the discovery of Services as explained in **[Services & Artifacts](../explanations/services-and-artifacts.md)**. Let’s do that using the public **[Open-Meteo 1.5.6 OpenAPI specification](https://github.com/open-meteo/open-meteo/blob/1.5.6/openapi/forecast.yml)** and its immutable raw URL: ```bash reshapr import -u https://raw.githubusercontent.com/open-meteo/open-meteo/1.5.6/openapi/forecast.yml ``` Example output (the generated identifier will differ): ```bash ✅ Import successful! ℹ️ Discovered Service Open-Meteo Weather Forecast API with ID: 0PXEW1ZDWFCZS ``` :::info You can also import local files into reShapr using the `-f` option. There’s one caveat, though: we’re not able to discover dependencies using this mode. ::: You can now list and check the discovered Service with the `service` command: ```bash ❯ reshapr service list ID NAME VERSION TYPE AGE 0PXEW1ZDWFCZS Open-Meteo Weather Forecast API 1.0 REST 19h ❯ reshapr service get 0PXEW1ZDWFCZS ℹ️ Service details ID : 0PXEW1ZDWFCZS Name : Open-Meteo Weather Forecast API Version : 1.0 Organization: yada Type : REST Created : 2026-03-28T19:06:26.029291 Operations : - Name: GET /v1/forecast ``` :::info In case of a mistake or unused Service, you can delete a service using the `reshapr service delete ` command. ::: ## Configuring consumption **[Configuration Plan](../explanations/configuration-and-exposition.md)** will allow you to define how your Service will be consumed by MCP Clients. You’ll define the **backend endpoint** the MCP Gateway will target as well as the **security options** for future expositions. Let’s create a simple configuration plan for the **[Open-Meteo Service](https://github.com/open-meteo/open-meteo/blob/1.5.6/openapi/forecast.yml)** we just imported. For that, we need the Service identifier we got just before (`0PXEW1ZDWFCZS`), and we need to know the public endpoint of this API (`https://api.open-meteo.com`). We’ll use the `config create` command and provide a basic name and description: ```bash reshapr config create 'open-meteo-manual' --description 'Manual Plan for Open-Meteo APIs' \ --serviceId 0PXEW1ZDWFCZS --backendEndpoint https://api.open-meteo.com ``` Example output (the generated identifier will differ): ```bash ✅ Configuration plan 'open-meteo-manual' created successfully with ID: 0PXPDMB4MFE6H ``` > Like the `service` command, you can also use sub-commands like `list`, `get` or `delete` to manage your configurations. ## Exposing an MCP Endpoint Exposing a Configuration Plan will allow you to define where your Service will be made available to MCP Clients. By creating an exposition, you’ll define the **Gateway Group** whose matching proxies will receive the configuration and expose the MCP endpoints. To create an exposition, we need the Configuration Plan identifier we got earlier (`0PXPDMB4MFE6H`) and the Gateway Group we want to target. The Default Gateway Group has the ID `1`. We can then use the `expo create` command for that: ```bash reshapr expo create --configuration 0PXPDMB4MFE6H --gateway-group 1 ``` Example output (identifiers, Gateway names, and endpoints will differ): ```bash ✅ Exposition created successfully with ID: 0PXPE6HPWFE4H ℹ️ Exposition details ID : 0PXPE6HPWFE4H Created on : 2026-03-29T12:44:22.327792751 Organization: yada Service: ID : 0PXEW1ZDWFCZS Name : Open-Meteo Weather Forecast API Version: 1.0 Type : REST Configuration Plan ID : 0PXPDMB4MFE6H Name : open-meteo-manual BackendEndpoint: https://api.open-meteo.com Included Ops. : [] Excluded Ops. : [] Gateway Group ID : 1 Name : Default Gateway Group Labels: {"env":"dev","team":"reshapr"} Gateway Endpoints - ID : 0PX4AF0BM0H7Z Name : prod-mcp-try-reshapr-proxy-7f8d7f6d89-c5jln Endpoints: mcp.try.reshapr.io/mcp/yada/Open-Meteo+Weather+Forecast+API/1.0 - ID : 0PX4AF4200HQG Name : prod-mcp-try-reshapr-proxy-7f8d7f6d89-jhvtd Endpoints: mcp.try.reshapr.io/mcp/yada/Open-Meteo+Weather+Forecast+API/1.0 ``` > Like the `service` command, you can also use sub-commands like `list`, `get` or `delete` to manage your configurations. 🎉 Congrats! You now have an MCP endpoint. Use the endpoint returned by the command, including its `https://` prefix, for the following verification. ## Verify the MCP endpoint Set the exact endpoint URL returned by `reshapr expo create`: ```bash export MCP_URL='https://mcp.try.reshapr.io/mcp//Open-Meteo+Weather+Forecast+API/1.0' ``` Discover the server using the stateless MCP `2026-07-28` protocol: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result | {supportedVersions, capabilities, serverInfo: ._meta["io.modelcontextprotocol/serverInfo"]}' ``` The response must include `2026-07-28` in `supportedVersions`. List the available Tools and confirm the Open-Meteo operation is present: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/list' \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result.tools[].name' ``` The list must contain `get_v1_forecast`. Call it and inspect the current weather returned by the backend: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header 'Mcp-Name: get_v1_forecast' \ --data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_v1_forecast","arguments":{"latitude":"48.8566","longitude":"2.3522","current":["temperature_2m","weather_code","wind_speed_10m"],"timezone":"Europe/Paris"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq -r '.result.content[0].text | fromjson | .current' ``` A JSON object containing the current temperature and weather values confirms that the Exposition, Gateway, and backend call work end to end. :::tip Compatibility reShapr also supports earlier MCP versions. Clients using `2025-11-25` or earlier use the legacy `initialize` handshake and a server-issued `MCP-Session-Id`. Session headers must not be used with stateless `2026-07-28` requests. ::: ## All-in-one Magic command 🪄 In case you didn’t take the shortcut, here’s the all-in-one command that does the same as above: ```bash reshapr import -u https://raw.githubusercontent.com/open-meteo/open-meteo/1.5.6/openapi/forecast.yml --backendEndpoint https://api.open-meteo.com ``` Example output (identifiers and endpoint will differ): ```bash ✅ Import successful! ℹ️ Discovered Service Open-Meteo Weather Forecast API with ID: 0PXEW1ZDWFCZS ✅ Exposition done! ✅ Exposition is now active! Exposition ID : 0PXPF1JQWFEF0 Organization : yada Created on : 2026-03-29T12:48:03.775297 Service ID : 0PXEW1ZDWFCZS Service Name : Open-Meteo Weather Forecast API Service Version: 1.0 Service Type : REST -> https://api.open-meteo.com Endpoints : mcp.try.reshapr.io/mcp/yada/Open-Meteo+Weather+Forecast+API/1.0 ``` 🎉 Congrats! You deployed an MCP Endpoint with just one CLI command! Use the returned endpoint with the verification steps above to confirm the one-command path end to end. ## Result You imported a versioned OpenAPI contract, created the Service, Configuration Plan, and Exposition, then proved the endpoint works with `server/discover`, `tools/list`, and a live `tools/call` response from Open-Meteo. ## Limits - The Configuration Plan in this tutorial leaves the MCP endpoint unauthenticated. Protect it before sharing its URL. - Open-Meteo is an external service with its own availability and usage terms; the returned weather values vary by time. - One successful Tool call validates this Exposition and operation, not every operation in the imported contract. ## Next step - **[Context Control in Practice](./context-control-in-practice.md)** turns this generated operation into a focused Tool and measures response filtering. - **[Test an MCP endpoint](../how-to-guides/test-mcp-endpoint.md)** with stateless or session-based clients and diagnose common errors. - **[Protect the endpoint with an API key](../how-to-guides/security/api-key.md)** and verify key rotation. --- ## Try reShapr online The fastest way to experience reShapr is through our hosted online environment: no installation required 🙌 Head over to **[try.reshapr.io](https://try.reshapr.io/)** to get started instantly. :::tip Watch the CLI setup The [11-second CLI installation demo](https://youtu.be/bmSPkisbqJo) previews the install command. Use this tutorial for the current authentication flow and verification. ::: ## Prerequisites - A GitHub or Google account - Node.js 20 or later to install the CLI - A browser allowed to open the CLI authentication callback ## Login workflow ### Step 0 - Use your preferred web browser And go to: ```shell https://try.reshapr.io ``` ![Step 0 — Use your preferred web browser and go to the reShapr Try](/img/docs/try-reshapr-online-0.png) ### Step 1 — Choose an authentication provider Select **GitHub** or **Google** to authenticate with the reShapr online try. ![Step 1 — Select GitHub or Google to authenticate with reShapr Try](/img/docs/try-reshapr-online-1.png) ### Step 2 — Sign in with your provider Enter your credentials. In this example, we are using GitHub. ![Step 2 — GitHub sign-in form for reShapr Try authentication](/img/docs/try-reshapr-online-2.png) ### Step 3 — Two-factor authentication If you have two-factor authentication enabled, complete the verification step. ![Step 3 — Two-factor authentication prompt during GitHub login](/img/docs/try-reshapr-online-3.png) ### Step 4 — Setup your try org name You can customize your trial organization (change its default name if you’d like). ![Step 4 — Customize your trial organization and follow the instructions](/img/docs/try-reshapr-online-4.png) Then install the CLI: ```shell npm install -g @reshapr/reshapr-cli --allow-scripts=@scarf/scarf ``` and log in to Reshapr online: ```shell reshapr login -s https://try.reshapr.io ``` ### Step 5 — Authenticate with the CLI When the browser opens, authorize the CLI — the token is valid for **2 hours**. ![Step 5 — Authorize the reShapr CLI in the browser](/img/docs/try-reshapr-online-5.png) ### Step 6 — You're all set! Login is successful. You can now use the CLI to import APIs, create expositions, and manage MCP servers. ![Step 6 — CLI login successful, ready to use reShapr](/img/docs/try-reshapr-online-6.png) ### Step 7 — Access the online dashboard Once authenticated, you can go back to `https://try.reshapr.io` and you'll land on the reShapr Try dashboard. ![Step 7 — reShapr Try online dashboard after successful login](/img/docs/try-reshapr-online-7.png) ## Result Your browser session and CLI are authenticated against the hosted reShapr environment. `reshapr info` should show your trial organization and server. ## Limits - The CLI token is valid for two hours. Run `reshapr login -s https://try.reshapr.io` again after it expires. - The hosted trial is shared infrastructure intended for evaluation, not production workloads. - Identifiers, organization names, endpoints, and dashboard contents differ between accounts. ## Next step Continue with **[Your First MCP Endpoint, End to End](./getting-started.md)**. It imports a versioned Open-Meteo contract, creates an Exposition in this environment, and calls the generated weather Tool. --- ## Web UI Quickstart: From Import to Exposition Use the Web UI Quick Start to turn a public OpenAPI 3 contract into an MCP endpoint. Along the way, you will inspect the imported Service, configure its backend, and expose it through a running proxy. ## Prerequisites - Access to a reShapr `1.0.0` Web UI and an account that can create Services, Configuration Plans, and Expositions - At least one running proxy registered as a Gateway in the **Default Gateway Group** - Outbound access from the control plane to GitHub and from the proxy to `https://api.open-meteo.com` This tutorial uses the immutable Open-Meteo `1.5.6` OpenAPI 3 contract: ```text https://raw.githubusercontent.com/open-meteo/open-meteo/1.5.6/openapi/forecast.yml ``` ## Sign in and open Quick Start Open your reShapr Web UI and sign in. From the dashboard, start **Quick Start**. The wizard has four stages: **Import**, **Artifacts**, **Plan**, and **Expose**. Keep it open until the final stage; each stage uses the resources created by the preceding one. ## Import the OpenAPI contract On the **Import** stage: 1. Select the remote URL import option. 2. Enter the Open-Meteo contract URL shown above. 3. Leave the Service name and version overrides empty. OpenAPI supplies both values through its `info` object. 4. Start the import. ![Quick Start Import stage with the Open-Meteo OpenAPI URL and no Artifact Secret selected](/img/docs/web-ui-quickstart-import.png) The result should identify a Service named **Open-Meteo Weather Forecast API**, version `1.0`, with its OpenAPI contract as the main Artifact. The Service identifier shown by your environment is generated and will differ from other installations. If the format is not recognized, confirm that you used the raw file URL and that the document declares OpenAPI 3.x. Swagger and OpenAPI 2.x documents are not supported in reShapr `1.0.0`. See **[Import OpenAPI, GraphQL, or Protobuf Artifacts](../how-to-guides/import-api-artifacts.md)** for format-specific troubleshooting. ## Review optional Artifacts Continue to **Artifacts**. This optional stage attaches reShapr-specific Prompts, Resources, Custom Tools, or output filters to the imported Service. Do not add an Artifact for this tutorial. Continue to **Plan** with only the main OpenAPI Artifact. You can add and select complementary Artifacts later with **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)**. ## Configure the backend On the **Plan** stage, enter this backend endpoint: ```text https://api.open-meteo.com ``` Keep all discovered operations included and continue. Quick Start creates a Configuration Plan named `default`, or updates that Plan if it already exists for this Service. ![Quick Start Plan stage with the Open-Meteo backend endpoint and endpoint security disabled](/img/docs/web-ui-quickstart-plan.png) The backend endpoint is where the proxy sends generated Tool calls. It is distinct from the OpenAPI contract URL used during import. For fine-grained operation or Artifact selection, use the advanced Plan editor instead of completing that configuration in Quick Start. ## Create the Exposition On the **Expose** stage: 1. Choose to expose the Plan now. 2. Leave endpoint security disabled for this first endpoint. 3. Confirm the Exposition. Quick Start targets Gateway Group ID `1`, the **Default Gateway Group**. The completed view should show the created Exposition and one or more MCP endpoint URLs supplied by connected proxies through their Gateway registrations. Endpoint URLs and resource identifiers are generated for your organization. A displayed URL can resemble this example: ```text https://mcp.example.com/mcp/acme/Open-Meteo+Weather+Forecast+API/1.0 ``` ![Active Open-Meteo MCP Server showing the default Plan, connected Gateway, backend, and endpoint URLs](/img/docs/web-ui-quickstart-result.png) If no endpoint appears, verify that a proxy is running and registered as a Gateway in the Default Gateway Group. Creating an Exposition stores the intended deployment, but only a connected proxy can publish a usable endpoint. If Quick Start reports that the Configuration Plan is already exposed on Gateway Group `1`, open the existing MCP Server instead. A Plan can have only one Exposition per Gateway Group; Quick Start does not create a duplicate. :::info Optional endpoint security Quick Start can protect the endpoint with an API key or OAuth 2.0. An API key is displayed only when it is generated, so store it before leaving the completion view. Add security after this tutorial by following **[Protect an MCP Endpoint with an API Key](../how-to-guides/security/api-key.md)** or **[Protect an MCP Endpoint with OAuth 2.0](../how-to-guides/security/oauth.md)**. ::: ## Verify the result Open the published MCP Server from the completion view or the dashboard's **MCP Servers** section. Confirm that it shows: - **Open-Meteo Weather Forecast API** version `1.0`; - the `default` Configuration Plan; - the Default Gateway Group; - at least one MCP endpoint URL. This confirms that the Web UI created the complete Service-to-Exposition resource chain. To verify MCP discovery and call the generated weather Tool, continue with **[Test an MCP Endpoint](../how-to-guides/test-mcp-endpoint.md)** and use the exact endpoint URL displayed for the registered Gateway. ## What you learned You used the Web UI to import an OpenAPI 3 contract, inspect its Service, configure its backend through the default Plan, and publish an Exposition. The same resource model underlies CLI, API, and GitOps workflows. ## Evidence and limits This tutorial was last verified with reShapr `1.0.0` on 2026-09-18. Its workflow follows the release-tagged **[Quick Start wizard](https://github.com/reshaprio/reshapr/blob/1.0.0/web-ui/src/lib/components/artifacts/QuickStartWizard.svelte)** and **[Artifact import form](https://github.com/reshaprio/reshapr/blob/1.0.0/web-ui/src/lib/components/artifacts/ImportArtifactForm.svelte)**. Quick Start uses the `default` Plan and Default Gateway Group rather than asking you to choose names or a target group. Use the full Plan and Exposition views when you need several Plans, another Gateway Group, or more control over the exposed MCP surface. --- ## Audit MCP Endpoint Calls Use this guide to enable audit on one Configuration Plan and verify the records produced by successful and rejected MCP requests. Audit is an endpoint policy: enabling proxy telemetry alone does not cause every Exposition to emit audit events. Audit events are [OpenTelemetry](https://opentelemetry.io/) log records marked with `log.type=audit`. The proxy exports them through its OpenTelemetry Logs pipeline; storage, routing, retention, and access control belong to the configured Collector and telemetry backends. ## Prerequisites You need: - a reShapr proxy and CLI at version `1.0.0`; - proxy OpenTelemetry logs configured with **[Observe the reShapr Proxy](./operations/observe-and-audit.md)**; - a telemetry or audit backend where you can search exported records; - `reshapr login` completed for the target organization; - an imported Service, its backend endpoint, and a Gateway Group ID; - `curl` and `jq`. Set the resource inputs: ```bash export SERVICE_ID='' export BACKEND_ENDPOINT='https://api.example.com' export GATEWAY_GROUP_ID='' ``` ## Create an audited Configuration Plan Create an API-key-protected Plan with audit enabled. Keep the structured response only long enough to extract its ID and generated key: ```bash CONFIG_JSON="$( reshapr config create 'audited-endpoint-check' \ --serviceId "${SERVICE_ID}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --apiKey \ --audit \ --output json )" export RESHAPR_CONFIG_ID="$(jq -er '.id' <<<"${CONFIG_JSON}")" export RESHAPR_API_KEY="$(jq -er '.apiKey' <<<"${CONFIG_JSON}")" unset CONFIG_JSON ``` The `--audit` option applies to every Exposition created from this Configuration Plan. It does not enable audit globally for other Plans. Create a named Exposition and extract its ID directly from the structured response: ```bash export EXPOSITION_ID="$( reshapr expo create \ --configuration "${RESHAPR_CONFIG_ID}" \ --gateway-group "${GATEWAY_GROUP_ID}" \ --name audited-endpoint-check \ --output json \ | jq -er '.exposition.id' )" reshapr expo get "${EXPOSITION_ID}" export MCP_URL='https:///mcp//audited-endpoint-check' ``` Wait until the Exposition lists the expected Gateway endpoint before sending requests. ## Generate audit events Send a successful discovery request with the generated key: ```bash export MCP_DISCOVERY_REQUEST='{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-audit-check","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' curl --fail --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "x-reshapr-key: ${RESHAPR_API_KEY}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" | jq '.result.supportedVersions' ``` Send the same request with an invalid key: ```bash curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header 'x-reshapr-key: deliberately-invalid' \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` The first request must return a JSON-RPC result and the second must return HTTP `401`. ## Inspect MCP call events Allow for Collector batching and export delay, then search the configured audit sink for `log.type=audit` and `event.action=server/discover`. An MCP call event can contain: | Attribute | Meaning | |---|---| | `log.type` | Always `audit` for an audit record | | `event.action` | MCP method, such as `server/discover` or `tools/call` | | `event.outcome` | `success` or `failure` | | `event.duration` | Gateway call duration in milliseconds | | `service.name`, `service.version` | Service exposed through the Plan | | `organization.id` | Owning organization | | `mcp.request.id` | JSON-RPC request ID, when present | | `mcp.session.id` | MCP session ID for session-based protocols, when present | | `mcp.target.name` | Tool or Resource name, when applicable | | `mcp.error.code` | JSON-RPC error code for a failed call, when present | | `mcp.response.size` | Response size in bytes | | `source.ip` | Caller address, when available | | `user.id` | Authenticated JWT subject, when available | | `trace.id` | Active trace ID, when available | The successful discovery request must have at least `event.outcome=success`, the Service and organization attributes, `mcp.request.id`, and `event.duration`. Call one known read-only Tool from the same Exposition to produce `event.action=tools/call`. Use `mcp.target.name`, `service.name`, and `trace.id` when present to correlate its audit record with Gateway and backend spans. ## Inspect authentication-failure events Search for `log.type=audit`, `event.action=authentication`, and the affected Service. Authentication-failure records can contain: | Attribute | Meaning | |---|---| | `event.outcome` | Always `failure` | | `event.reason` | Rejection reason, such as `invalid_api_key`, `missing_bearer_token`, `invalid_token`, or `missing_scope` | | `http.response.status_code` | HTTP rejection status | | `service.id`, `service.name`, `service.version` | Affected Service | | `organization.id` | Owning organization | | `source.ip` | Caller address, when available | | `trace.id` | Active trace ID, when available | The rejected request in this guide must have `event.reason=invalid_api_key` and `http.response.status_code=401`. Application logs explain runtime behavior, traces connect work across boundaries, and audit records answer which MCP action was attempted and with what outcome. Do not treat audit as a substitute for application diagnostics or distributed tracing. ## Clean up Delete the temporary Exposition before its Configuration Plan: ```bash reshapr expo delete "${EXPOSITION_ID}" reshapr config delete "${RESHAPR_CONFIG_ID}" unset RESHAPR_API_KEY MCP_DISCOVERY_REQUEST ``` ## Result The Configuration Plan enables audit for its Expositions, and the audit sink contains distinct successful-call and authentication-failure records marked with `log.type=audit`. ## Limits - Audit must be enabled independently on every Configuration Plan that requires it. - Audit records are emitted only when OpenTelemetry Logs are available and export succeeds. - Optional identity, source, session, target, error, and trace attributes depend on the request and authentication mode. - Audit records can contain identifiers and network metadata. Protect their transport, storage, access, retention, and deletion according to your security requirements. - reShapr does not provide an audit database, SIEM, compliance policy, or immutable retention mechanism. ## Next step Use **[Observe the reShapr Proxy](./operations/observe-and-audit.md)** to route records with `log.type=audit` to a dedicated sink. Use **[Security Capabilities and Limits](../explanations/security-model.md)** to review endpoint authentication and audit boundaries. The release-tagged [audit implementation](https://github.com/reshaprio/reshapr/tree/1.0.0/proxy/src/main/java/io/reshapr/proxy/audit) and [public API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml) remain the canonical references. --- ## Automate reShapr with the CLI in CI/CD Use this guide to import or update an API definition from a CI job without parsing human-readable output. The workflow authenticates through standard input, records existing state, runs the idempotent import path, and fails unless the resulting Service, Configuration Plan, and Exposition satisfy explicit postconditions. reShapr provides scriptable CLI and API operations. It does not provide a native CI/CD pipeline, automatic drift detection, deployment approval, or rollback orchestration. ## Prerequisites You need: - Node.js 20 or later and reShapr CLI `1.0.0` installed in the job image; - a reShapr `1.0.0` control plane and a running proxy registered as a Gateway in the default Gateway Group; - a dedicated automation identity restricted to the target organization; - the identity password stored as a masked CI secret; - an OpenAPI 3, GraphQL, or Protobuf source tracked by the build; - a backend endpoint reachable from the selected proxy; - Bash and `jq`. This example uses password authentication for an on-premises control plane. Adapt the authentication stage when your deployment uses another supported identity flow. Set non-sensitive job inputs as CI variables: ```bash export RESHAPR_SERVER='https://app.reshapr.example.com' export RESHAPR_USERNAME='ci-release' export RESHAPR_ORGANIZATION='production' export SERVICE_NAME='Weather API' export SERVICE_VERSION='1.0.0' export API_DEFINITION='openapi/weather.yaml' export BACKEND_ENDPOINT='https://weather-api.production.example.com' ``` Expose the password to the job as `RESHAPR_PASSWORD` through the CI platform's secret mechanism. Do not commit it or include it in a command argument. ## Prepare an isolated CLI home The CLI stores its session under `$HOME/.reshapr/config`. Give each job an isolated home directory and restrict its permissions: ```bash export HOME="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/reshapr-ci-${CI_JOB_ID:-$$}" mkdir -p "${HOME}" chmod 700 "${HOME}" ``` Do not cache or publish this directory as a job artifact. Remove it in the CI platform's unconditional cleanup stage. ## Authenticate without a password argument Disable shell tracing before reading or forwarding a secret. Pipe the password to `--password-stdin`: ```bash set +x printf '%s\n' "${RESHAPR_PASSWORD}" \ | reshapr login \ --username "${RESHAPR_USERNAME}" \ --password-stdin \ --org "${RESHAPR_ORGANIZATION}" \ --server "${RESHAPR_SERVER}" unset RESHAPR_PASSWORD reshapr switch-org "${RESHAPR_ORGANIZATION}" ``` The CLI writes its bearer token to a mode-`0600` configuration file. `switch-org` replaces it with a token for the target organization and fails if the user is not a member. Confirm that subsequent authenticated calls work: ```bash reshapr info ``` An invalid credential, unreachable server, expired token, or malformed response causes the CLI to exit non-zero. Stop the job rather than treating those failures as an absent resource. ## Record existing state Start the deployment stage in strict mode: ```bash set -euo pipefail ``` Request structured output and normalize the empty-account case because `service list --output json` writes no document when the organization has no Services: ```bash SERVICE_LIST="$(reshapr service list --output json)" if [[ -z "${SERVICE_LIST}" ]]; then SERVICE_LIST='[]' fi EXISTING_SERVICE_ID="$( jq -er \ --arg name "${SERVICE_NAME}" \ --arg version "${SERVICE_VERSION}" \ 'first(.[] | select(.name == $name and .version == $version) | .id) // ""' \ <<<"${SERVICE_LIST}" )" if [[ -n "${EXISTING_SERVICE_ID}" ]]; then printf 'Updating Service %s\n' "${EXISTING_SERVICE_ID}" else printf 'Creating Service %s:%s\n' "${SERVICE_NAME}" "${SERVICE_VERSION}" fi ``` An empty lookup is expected state. A failed `reshapr service list` is not: strict mode stops the job before any deployment command runs. ## Import or update the Service Run the same command for creation and update: ```bash IMPORT_RESULT="$( reshapr import \ --file "${API_DEFINITION}" \ --serviceName "${SERVICE_NAME}" \ --serviceVersion "${SERVICE_VERSION}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --output json )" ``` For the same Service name and version, release `1.0.0` updates the imported Service instead of creating a duplicate. With `--backendEndpoint`, it creates the `default` Configuration Plan and Exposition when absent, then reuses them on later imports. Validate the complete structured result before using any identifier: ```bash jq -e \ --arg name "${SERVICE_NAME}" \ --arg version "${SERVICE_VERSION}" \ --arg backend "${BACKEND_ENDPOINT}" \ '.service.id != null and .service.name == $name and .service.version == $version and .configurationPlan.id != null and .configurationPlan.name == "default" and .configurationPlan.backendEndpoint == $backend and .exposition.id != null and (.endpoints | length > 0)' \ <<<"${IMPORT_RESULT}" >/dev/null export SERVICE_ID="$(jq -er '.service.id' <<<"${IMPORT_RESULT}")" export CONFIGURATION_PLAN_ID="$(jq -er '.configurationPlan.id' <<<"${IMPORT_RESULT}")" export EXPOSITION_ID="$(jq -er '.exposition.id' <<<"${IMPORT_RESULT}")" unset IMPORT_RESULT SERVICE_LIST ``` If the existing `default` Plan targets another backend, `import --backendEndpoint` deliberately retains that value. The assertion then fails. Changing an existing Plan through `reshapr config update` opens an interactive editor in `1.0.0`; use a separately reviewed API operation or a controlled replacement workflow instead of automating the editor. ## Verify the postconditions Confirm that a previous lookup, when present, resolved to the same Service: ```bash if [[ -n "${EXISTING_SERVICE_ID}" && "${EXISTING_SERVICE_ID}" != "${SERVICE_ID}" ]]; then printf 'Service identity changed unexpectedly\n' >&2 exit 1 fi ``` Fetch the Service and Exposition independently. `service get` emits the Service directly. `expo get` combines the Exposition with its active Gateways and computed endpoints: ```bash reshapr service get "${SERVICE_ID}" --output json \ | jq -e \ --arg id "${SERVICE_ID}" \ --arg name "${SERVICE_NAME}" \ --arg version "${SERVICE_VERSION}" \ '.id == $id and .name == $name and .version == $version' \ >/dev/null EXPOSITION="$(reshapr expo get "${EXPOSITION_ID}" --output json)" jq -e \ --arg id "${EXPOSITION_ID}" \ --arg serviceId "${SERVICE_ID}" \ --arg planId "${CONFIGURATION_PLAN_ID}" \ '.exposition.id == $id and .exposition.service.id == $serviceId and .exposition.configurationPlan.id == $planId and (.gateways | length > 0)' \ <<<"${EXPOSITION}" >/dev/null export MCP_URL="$(jq -er '.endpoints[0]' <<<"${EXPOSITION}")" unset EXPOSITION EXISTING_SERVICE_ID printf 'Verified active MCP endpoint: %s\n' "${MCP_URL}" ``` Finish with **[Test an MCP Endpoint](./test-mcp-endpoint.md)**. A real read-only `tools/call` validates the Gateway route and backend behavior that control-plane resource checks cannot prove. ## Control destructive changes Do not use `--force` as the impact-assessment mechanism. For an Artifact removal, query the public deletion-impact endpoint first and evaluate its structured response against your deployment policy. The following helper sends the saved bearer token through curl configuration on standard input, keeping it out of the curl argument list: ```bash export ARTIFACT_ID='' RESHAPR_TOKEN="$(jq -er '.token' "${HOME}/.reshapr/config")" set +x DELETION_IMPACT="$( { printf 'header = "Authorization: Bearer %s"\n' "${RESHAPR_TOKEN}" printf 'url = "%s/api/v1/artifacts/%s/deletion-impact"\n' \ "${RESHAPR_SERVER}" "${ARTIFACT_ID}" } | curl --silent --show-error --fail-with-body --config - )" unset RESHAPR_TOKEN ``` This example permits deletion only when no Configuration Plan references the Artifact: ```bash jq -e '(.impactedPlans // []) | length == 0' \ <<<"${DELETION_IMPACT}" >/dev/null reshapr artifact delete "${ARTIFACT_ID}" --force unset DELETION_IMPACT ``` Replace the `jq` expression with a reviewed policy when selected Plans may be changed. Deleting the last selected Artifact from a Plan can make that Plan fall back to all attached Artifacts, widening its MCP surface. Verify affected Plans and `tools/list` after any approved deletion. The CLI reports expected absence and other HTTP failures with the same non-zero exit status for many commands. When a pipeline must distinguish HTTP `404` from authentication, authorization, validation, quota, or server failures, call the release-tagged public API contract directly and branch on its status code. Do not infer absence from an arbitrary CLI failure message. ## Clean up job credentials Remove the isolated CLI state from an unconditional cleanup stage, including after a failed deployment: ```bash rm -rf "${HOME}/.reshapr" ``` Use the CI platform's protected workspace cleanup rather than relying only on this final command. ## Result The job authenticates without a password argument, observes existing state, imports or updates one Service through structured CLI output, preserves the default Plan and Exposition identities, and fails when its explicit postconditions are not met. ## Limits - This guide supplies a portable Bash workflow, not a ready-made pipeline for a specific CI product. - `import --backendEndpoint` reuses an existing `default` Plan and does not update its backend endpoint. - `reshapr config update` is interactive in `1.0.0` and is unsuitable for an unattended job. - The default import-and-expose path targets the built-in default Gateway Group. Use explicit Plan and Exposition API operations when another group is required. - The CLI does not provide a universal create-or-update command for every resource type. - Structured output does not provide transactionality, locking, drift detection, approval, retry, or rollback semantics. - Concurrent jobs targeting the same resource require serialization or conflict handling owned by the pipeline. ## Next step Use **[Manage reShapr Resources with GitOps](./manage-resources-with-gitops.md)** when Kubernetes controllers should own desired state. Review **[Product Interfaces](../references/interfaces.md)** to choose between the CLI, public APIs, Web UI, and Kubernetes controllers. The release-tagged [CLI implementation](https://github.com/reshaprio/reshapr/tree/1.0.0/cli) and [public API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml) remain the canonical interface references. --- ## Build a Scripted Custom Tool Create one MCP Tool that calls two existing Tools in parallel, combines their results, and handles required and optional failures differently. Then verify the script allow-list and runtime guardrails. ## What you are building The goal is an agent-facing action named `inspect_todo_and_owner`. Given a Todo ID and an expected owner ID, it fetches both records, checks whether the owner matches, and returns one compact result. The Todo is required, while a failed owner lookup becomes a warning rather than failing the complete action. ```mermaid sequenceDiagram autonumber actor Agent participant MCP as reShapr MCP endpoint participant Script as inspect_todo_and_owner participant Todos as Todos Tool participant Users as Users Tool (cross-Service) Agent->>MCP: tools/call inspect_todo_and_owner MCP->>Script: Run with declared Tool allow-list par Required Todo lookup Script->>Todos: get_todos_todoid(todoId) Todos-->>Script: Todo or fault and Optional owner lookup Script->>Users: get_users_userid(userId) Users-->>Script: User or fault end alt Todo lookup failed Script-->>MCP: rs.fail(message, details) else Todo lookup succeeded Script-->>MCP: Todo, owner match, and warnings end MCP-->>Agent: One MCP Tool result ``` An LLM could discover the two primitive Tools, call them itself, carry the intermediate results in its context, correlate their identifiers, and decide how to handle each failure. A Scripted Custom Tool moves that stable workflow into reviewed infrastructure instead. The call graph, concurrency, data combination, and failure policy become deterministic; the Agent plans one Tool call rather than repeatedly reconstructing the orchestration. For this two-call example, parallel execution can avoid serial backend latency and the compact final result can reduce agent-facing calls and intermediate context. The actual latency and token savings remain workload-dependent and should be measured rather than assumed. **[From API Sprawl to Agent Actions](/blog/from-api-sprawl-to-agent-actions)** presents a fuller applied scenario built around the same task-shaped orchestration pattern. Use this pattern when the sequence represents a reusable business action. Keep orchestration in the Agent when tool choice or call order is intentionally dynamic and depends on open-ended reasoning. ## Prerequisites - reShapr CLI `1.0.0`, authenticated against reShapr Online or a local `1.0.0` environment - `curl` and `jq` - Docker Compose v2 when testing the timeout override on the local stack - A running proxy registered as a Gateway in Gateway Group `1` - Outbound access from the control plane to `https://reshapr.io` and from the proxy to `https://jsonplaceholder.typicode.com` The example uses two small OpenAPI contracts hosted with this documentation. [JSONPlaceholder](https://jsonplaceholder.typicode.com) supplies the public backend data, so no backend credentials are required. ## Import the two Services Import the Todos and Users contracts: ```bash reshapr import --url 'https://reshapr.io/examples/scripted-custom-tools/todos-openapi.yaml' reshapr import --url 'https://reshapr.io/examples/scripted-custom-tools/users-openapi.yaml' ``` Locate the generated Services using structured CLI output: ```bash TODOS_SERVICE_ID="$( reshapr service list --output json \ | jq -er 'map(select(.name == "Scripted Todos API" and .version == "1.0.0")) | first | .id' )" USERS_SERVICE_ID="$( reshapr service list --output json \ | jq -er 'map(select(.name == "Scripted Users API" and .version == "1.0.0")) | first | .id' )" export TODOS_SERVICE_ID USERS_SERVICE_ID ``` For REST Services, reShapr derives Tool names from the HTTP method and path. These contracts produce `get_todos_todoid` and `get_users_userid`. ## Expose the cross-Service target A scripted cross-Service call resolves the target Service's elected Exposition in the same organization. Create and expose the Users Configuration Plan before running the Todos workflow: ```bash USERS_PLAN_ID="$( reshapr config create 'scripted-users-target' \ --serviceId "$USERS_SERVICE_ID" \ --backendEndpoint 'https://jsonplaceholder.typicode.com' \ --includedOperations '["GET /users/{userId}"]' \ --output json \ | jq -er '.id' )" reshapr expo create \ --configuration "$USERS_PLAN_ID" \ --gateway-group 1 ``` The Exposition makes `Scripted Users API:1.0.0` resolvable by scripts running in the same organization. It does not expose that Service to scripts in another organization. ## Attach the scripted Artifact Review the complete **[Scripted Custom Tool Artifact](/examples/scripted-custom-tools/todo-workflow.yaml)**. Its main Tool, `inspect_todo_and_owner`, declares two allowed calls: ```yaml tools: - tool: get_todos_todoid - service: Scripted Users API:1.0.0 tool: get_users_userid ``` An omitted `service` means the same Service as the Custom Tool. A coordinate in the `:` form selects another Service in the same organization. Attach the Artifact and retain its generated name: ```bash SCRIPT_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/scripted-custom-tools/todo-workflow.yaml' \ --output json \ | jq -er '.name' )" export SCRIPT_ARTIFACT_NAME ``` The `tools` list is an execution allow-list for the script. It does not grant OAuth scopes or implement authorization for individual MCP Tools. Endpoint access remains governed by the Exposition's authentication configuration. ## Expose the scripted Tools Create a Todos Plan that selects the Artifact, its four scripted capabilities, and the generated operation called by the same-Service script: ```bash SCRIPTED_TOOLS='[ "GET /todos/{todoId}", "inspect_todo_and_owner", "probe_undeclared_tool", "exceed_call_limit", "recurse_until_limited" ]' SCRIPTED_ARTIFACTS="$(jq -cn --arg artifact "$SCRIPT_ARTIFACT_NAME" '[$artifact]')" TODOS_PLAN_ID="$( reshapr config create 'scripted-todo-workflow' \ --serviceId "$TODOS_SERVICE_ID" \ --backendEndpoint 'https://jsonplaceholder.typicode.com' \ --includedOperations "$SCRIPTED_TOOLS" \ --includedArtifacts "$SCRIPTED_ARTIFACTS" \ --output json \ | jq -er '.id' )" TODOS_MCP_ENDPOINT="$( reshapr expo create \ --configuration "$TODOS_PLAN_ID" \ --gateway-group 1 \ --output json \ | jq -er '.endpoints[0]' )" export TODOS_MCP_ENDPOINT unset SCRIPTED_TOOLS SCRIPTED_ARTIFACTS ``` Scripted calls follow the same Plan operation selection as direct MCP calls. Keeping `GET /todos/{todoId}` selected therefore also leaves its generated `get_todos_todoid` Tool visible on this Exposition. The cross-Service `get_users_userid` Tool is selected on the Users Plan but is not added to the Todos endpoint. Set the scheme for your environment. reShapr Online uses `https`; the local Compose proxy uses `http`: ```bash export MCP_URL="https://$TODOS_MCP_ENDPOINT" ``` If the returned endpoint already includes a scheme, assign it directly instead. ## List and call the workflow Tool Define a helper that sends a stateless MCP `tools/call` request: ```bash call_tool() { local tool_name="$1" local arguments="$2" local request request="$(jq -cn \ --arg name "$tool_name" \ --argjson arguments "$arguments" \ '{ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: $name, arguments: $arguments, _meta: { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": {name: "reshapr-docs", version: "1.0.0"}, "io.modelcontextprotocol/clientCapabilities": {} } } }')" curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header "Mcp-Name: $tool_name" \ --data "$request" \ "$MCP_URL" } ``` First confirm the scripted capabilities are exposed: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/list' \ --data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-docs","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq -r '.result.tools[].name' ``` Call the main Tool: ```bash call_tool 'inspect_todo_and_owner' '{"todoId":1,"userId":1}' \ | jq -r '.result.content[0].text | fromjson' ``` `rs.callToolAsync(...)` starts both backend calls, and `rs.awaitPromises(...)` returns their results in declaration order. The response contains the compact Todo, its owner, an `ownerMatches` flag, and an empty `warnings` array. ## Handle partial and terminal failures The owner lookup is optional to the final result. Requesting a missing user demonstrates a partial failure: ```bash call_tool 'inspect_todo_and_owner' '{"todoId":1,"userId":999}' \ | jq -r '.result.content[0].text | fromjson' ``` The Tool still succeeds with `owner: null` and a warning. By contrast, the Todo is required. A missing Todo calls `rs.fail(...)` with structured context: ```bash call_tool 'inspect_todo_and_owner' '{"todoId":999,"userId":1}' | jq .result ``` The MCP result has `isError: true`; its content identifies the Todo and preserves the underlying failure details. ## Verify the script guardrails ### Allow-list `probe_undeclared_tool` attempts a cross-Service call that is deliberately absent from its `tools` list: ```bash call_tool 'probe_undeclared_tool' '{}' \ | jq -r '.result.content[0].text | fromjson' ``` The returned object has `blocked: true` and an invalid-parameters error naming the undeclared Tool. This check applies to script execution only; it is not an OAuth authorization decision. ### Maximum Tool calls The `1.0.0` default permits 10 underlying Tool calls per script execution. The diagnostic Tool attempts 11: ```bash call_tool 'exceed_call_limit' '{}' \ | jq -r '.result.content[0].text | fromjson' ``` The result reports `completedCalls: 10`, `blockedAt: 11`, and the guardrail error. ### Maximum nesting depth The default nesting depth is 5. Ask the recursive scripted Tool to exceed it: ```bash call_tool 'recurse_until_limited' '{"remaining":10}' \ | jq -r '.result.content[0].text | fromjson' ``` The final content has `reachedGuardrail: true` and a `detail` containing the maximum-depth fault returned by the rejected nested call. ### Execution timeout The default script timeout is 10,000 ms. To exercise this guardrail with the release-pinned local Compose stack, create an override named `scripted-timeout.override.yaml`: ```yaml services: gateway-01: environment: RESHAPR_GATEWAY_SCRIPTING_TIMEOUT: 1 ``` Recreate only the proxy with the one-millisecond test limit: ```bash docker compose \ --file "$HOME/.reshapr/docker-compose-1.0.0.yml" \ --file scripted-timeout.override.yaml \ up --detach --force-recreate gateway-01 ``` Wait until `http://localhost:7777/q/health/ready` reports `UP`, then call `inspect_todo_and_owner` again. The MCP result has `isError: true` and reports `Custom tool script timed out`. Restore the release default and remove the temporary override: ```bash docker compose \ --file "$HOME/.reshapr/docker-compose-1.0.0.yml" \ up --detach --force-recreate gateway-01 rm scripted-timeout.override.yaml ``` For another deployment model, set `RESHAPR_GATEWAY_SCRIPTING_TIMEOUT` in the proxy workload and perform its normal rolling restart. The timeout cancellation is best effort: it interrupts blocking backend calls and waits, but it cannot forcibly stop a pure CPU loop in the JavaScript engine. ## Result The Todos MCP endpoint exposes one stable composed action alongside the same-Service generated Tool required by that action. A successful call returns one compact result; optional failures remain visible as warnings, required failures use a structured MCP Tool error, and undeclared or excessive calls are rejected by the proxy. ## Limits - Scripts can call reShapr Tools only; they cannot make arbitrary network requests. - Cross-Service calls are limited to the same organization and use the target Service's elected Exposition. - Every Tool a script may call must appear in its `tools` allow-list, including scripted Tools used for nesting. - A Tool called on the same Service must also remain selected by the current Plan and is consequently visible to MCP clients. - The allow-list is not per-Tool OAuth authorization. OAuth scopes apply to the Exposition before MCP dispatch. - Script timeout, call-count, and depth limits are proxy settings and affect every scripted Tool served by that proxy. - JSONPlaceholder is a public test backend. Do not use its data or availability as a production dependency. - Re-running the commands with the same Plan or Exposition names requires deleting or renaming the previous resources. ## Next step - **[Custom Tools](../references/custom-tools-specification.md#scripted-custom-tools)** is the field and `rs` API reference. - **[Attach and Select reShapr Artifacts](./select-reshapr-artifacts.md)** explains capability extraction and Plan-level Artifact selection. - **[Authenticate Backend Calls and Use Elicitation](./security/backend-auth-and-elicitation.md)** adds credentials to underlying Tool calls. - **[Observe and Audit MCP Calls](./operations/observe-and-audit.md)** configures proxy telemetry for the resulting workflow. --- ## Connect the Control Plane to an OIDC Provider Use an external OpenID Connect (OIDC) provider when users must authenticate to the reShapr control plane through your organization's identity system. The same configuration enables browser login for the Web UI and `reshapr login`. This is control-plane user authentication. It is separate from **[OAuth protection for an MCP endpoint](./security/oauth.md)** and from OAuth credentials used to call a backend. :::warning Remaining security limitation The control plane validates final redirect URIs, uses opaque single-use login and onboarding states stored in Hazelcast, verifies the ID-token `nonce`, and rejects expired ID and access tokens. However, it does not bind the login state to the initiating browser session. It also reads identity claims by decoding the returned access token without independently verifying its JWT signature, issuer, or audience. Do not treat this flow as suitable for an untrusted, publicly reachable production login until those checks are added. Registering an exact callback URI at the identity provider and configuring the reShapr redirect allow-list are separate requirements. ::: ## Prerequisites You need: - reShapr control-plane and Web UI images `1.0.0`, with control-plane chart `0.0.14`; - an OIDC provider reachable from user browsers and the control-plane pods; - permission to register an [OAuth 2.0 Authorization Code](https://www.rfc-editor.org/rfc/rfc6749#section-4.1) client at that provider; - public HTTPS URLs for the control plane and, when used, the Web UI; - `helm`, `kubectl`, `curl`, and `jq`; - an existing reShapr organization if first-time users should bypass onboarding. Set the deployment inputs used below: ```bash export RESHAPR_NAMESPACE='reshapr-system' export RESHAPR_CTRL_PUBLIC_URL='https://ctrl.reshapr.example.com' export RESHAPR_WEBUI_PUBLIC_URL='https://app.reshapr.example.com' export OIDC_AUTHORIZATION_ENDPOINT='https://idp.example.com/realms/platform/protocol/openid-connect/auth' export OIDC_TOKEN_ENDPOINT='https://idp.example.com/realms/platform/protocol/openid-connect/token' ``` The authorization endpoint is opened by the user's browser. The token endpoint is called by the control plane and can use a different network address when your identity provider supports that topology. ## Register the OIDC client Create a confidential OIDC client at the identity provider with: - Authorization Code as the allowed grant; - this exact redirect URI: ```text https://ctrl.reshapr.example.com/auth/callback/oidc ``` - the standard `openid`, `profile`, and `email` scopes; - ID and access tokens encoded as compact JWTs with an `exp` claim; - `preferred_username` in the access token; - `email` in the access token when a new user can enter onboarding. reShapr requires both tokens returned by the token endpoint. It verifies the login `nonce` and expiration on the `id_token`, verifies expiration on the `access_token`, and reads identity and authorization claims from the `access_token`. Configure claim mappers accordingly. Optional access and organization rules can also require `groups` or custom claims. Store the client credentials without placing the secret in the Helm values file: ```bash read -r -p 'OIDC client ID: ' OIDC_CLIENT_ID read -r -s -p 'OIDC client secret: ' OIDC_CLIENT_SECRET; printf '\n' kubectl --namespace "${RESHAPR_NAMESPACE}" apply -f - <' --force ``` If you created the Gateway Group only for this procedure and no other Exposition uses it, remove it: ```bash reshapr gateway-group delete "${GATEWAY_GROUP_ID}" ``` Deleting the token prevents later registration with that credential. A proxy that is already running can retain its last synchronized configuration during a control-plane connectivity loss, so stop the container as well when access must end immediately. ## Result You now have a reShapr `1.0.0` proxy running in another trust domain, registered as a logical Gateway with a dedicated credential, selected through Gateway Group labels, and verified through its MCP endpoint. ## Limits - The topology does not by itself prove data residency or compliance. Validate DNS, routing, proxies, identity providers, observability exporters, and backend dependencies. - Proxy-to-control-plane TLS depends on `RESHAPR_CTRL_TLS_PLAINTEXT=false` and a correctly configured control-plane TLS endpoint. MCP client ingress and backend TLS are separate boundaries. - Remote MCP clients need a TLS ingress, load balancer, or reverse proxy. Configure it first, then advertise its host and optional port through `RESHAPR_GATEWAY_FQDNS` instead of `localhost:7777`. - Live configuration propagation is not a zero-downtime upgrade or rollback guarantee. - A production deployment also needs durable secret injection, ingress TLS, resource limits, health supervision, logging, and an image update policy. ## Next step Read **[Deployment Models and Trust Boundaries](../explanations/deployment-models-trust-boundaries.md)** for the complete traffic map and **[Control Plane to Proxy Synchronization](../explanations/control-plane-gateway-synchronization.md)** for registration and recovery behavior. For production Kubernetes controls, continue with **[Deploy reShapr on Kubernetes for Production](./deploy-kubernetes-production.md)**. --- ## Deploy reShapr on Kubernetes for Production Deploy the four reShapr Helm charts with explicit release tags, external persistence, Kubernetes Secrets, TLS ingress, and workload availability controls. This is a production-oriented starting point, not a universal production certification. Adapt capacity, topology, policies, and recovery procedures to your platform requirements. ## Prerequisites - Kubernetes 1.25 or later and Helm 3.8 or later - Cluster-admin access for CRDs, cluster-scoped RBAC, and admission configuration - An ingress controller and a certificate-management process - An externally managed PostgreSQL service with tested backup and restore procedures - DNS names for the control plane, Web UI, and MCP proxy - A metrics pipeline when enabling the proxy HPA - Prometheus Operator CRDs when enabling `ServiceMonitor` - reShapr CLI `1.0.0` with administrative access after the control plane starts This guide uses Helm charts `0.0.14`, controllers `0.0.3`, and runtime images `1.0.0`. The charts default to `nightly`, so the overrides below pin the reviewed release candidates instead. The [chart release](https://github.com/reshaprio/reshapr-helm-charts/releases/tag/0.0.14) owns packaging. The [reShapr release](https://github.com/reshaprio/reshapr/releases/tag/1.0.0) owns runtime behavior. ## 1. Choose the topology This guide shows all four charts as separate Helm releases. Select the optional components according to your operating model: | Release | Namespace | Role | |---|---|---| | `reshapr-control-plane` | `reshapr-system` | APIs, configuration, authentication, and database access | | `reshapr-ui` | `reshapr-system` | Optional administration Web UI | | `reshapr-controllers` | `reshapr-system` | Optional operator and admission webhook | | `reshapr-proxy` | `reshapr-proxies` | MCP data plane and backend dispatch | :::info These are the default namespaces, not fixed requirements. You can change them to match your cluster conventions and deploy multiple proxy releases in different namespaces, for example to isolate environments, teams, or Gateway Groups. ::: `reshapr-ui` is optional when administrators use the CLI or APIs instead. Consider `reshapr-controllers` when you want to reconcile reShapr resources from Kubernetes manifests as part of a **[GitOps workflow](../tutorials/first-gitops-mcp-endpoint.md)**, or when you want the admission webhook to inject a reShapr proxy as a sidecar container into application Pods. See **[Kubernetes APIs and Controllers](../references/kubernetes-apis.md#admission-controller)** for the boundaries of both controller modes. The control-plane chart can embed the Web UI as a subchart. Separate releases make independent rollout and ownership explicit; use the composed option when one release lifecycle is more appropriate for your platform. ## 2. Provision external dependencies and Secrets Create the namespaces first: ```bash kubectl create namespace reshapr-system kubectl create namespace reshapr-proxies ``` Provision these Secrets with your external secret manager, encrypted Git workflow, or another organization-approved mechanism: | Namespace | Secret | Required keys | Consumer | |---|---|---|---| | `reshapr-system` | `reshapr-db-credentials` | `password` | Control plane external PostgreSQL | | `reshapr-system` | `reshapr-admin-credentials` | `name`, `password`, `email`, `default-gateway-tokens` | Initial administrator and Gateway token | | `reshapr-system` | `reshapr-api-key-secret` | `api-key` | Control plane admin API | | `reshapr-system` | `reshapr-encryption-key-secret` | `encryption-key-v1`; `encryption-key` only while migrating legacy values | Sensitive data encryption | | `reshapr-system` | `reshapr-jwt-keys-secret` | `private-key.pem`, `public-key.pem` | JWT signing and verification | | `reshapr-system` | `reshapr-web-ui-api-key` | `api-key` | Web UI server-side API access | | `reshapr-proxies` | `reshapr-gateway-token` | `token` | Proxy registration with the control plane | Generate each AES-256 key as 32 random bytes encoded with Base64, for example with `openssl rand -base64 32`. Do not pass secret values with Helm `--set`: they can remain in shell history and Helm release metadata. The [control-plane values](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/control-plane/values.yaml), [proxy values](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/proxy/values.yaml), and [Web UI values](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/web-ui/values.yaml) define the exact Secret contracts. ## 3. Configure the control plane Create `values/control-plane.yaml` with environment-specific hosts, resource sizing, and scheduling rules. This bounded example shows the required production decisions without replacing the chart reference: ```yaml ctrl: replicaCount: 3 image: tag: "1.0.0" pullPolicy: IfNotPresent resources: requests: cpu: 500m memory: 1Gi limits: cpu: "1" memory: 1Gi podDisruptionBudget: enabled: true minAvailable: 2 postgresql: enabled: false externalDatabase: host: postgresql-ha.database.svc.cluster.local port: 5432 database: reshapr username: reshapr existingSecret: reshapr-db-credentials passwordKey: password admin: existingSecret: reshapr-admin-credentials apiKey: existingSecret: reshapr-api-key-secret encryptionKey: existingSecret: reshapr-encryption-key-secret activeKeyId: v1 keys: v1: key: encryption-key-v1 jwtKeys: existingSecret: reshapr-jwt-keys-secret ingress: enabled: true className: "" ctrl: host: app.reshapr.example.com paths: - path: / pathType: Prefix tls: - secretName: reshapr-ctrl-tls hosts: - app.reshapr.example.com ``` Replace `` with an `IngressClass` supported by your Kubernetes platform. List the available classes with `kubectl get ingressclass`, then follow that controller's documentation for any required annotations or TLS behavior. The reShapr charts create standard `networking.k8s.io/v1` Ingress resources but do not install or qualify an ingress controller. Add pod anti-affinity, topology spread, tolerations, and node selection according to your cluster. The release-tagged [`values-production.yaml`](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/control-plane/values-production.yaml) provides a larger example, but keep the `1.0.0` image override above. Install the release: ```bash helm upgrade --install reshapr-control-plane \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-control-plane \ --version 0.0.14 \ --namespace reshapr-system \ --values values/control-plane.yaml ``` Wait for the deployment, check its public health endpoint, then authenticate the CLI and confirm the runtime version: ```bash kubectl rollout status deployment/reshapr-control-plane-ctrl \ --namespace reshapr-system --timeout 5m curl --fail --silent https://app.reshapr.example.com/q/health/ready | jq reshapr login --server https://app.reshapr.example.com reshapr info ``` The reported server version must be `1.0.0` before continuing. ## 4. Configure the Web UI Create `values/web-ui.yaml`: ```yaml replicaCount: 2 image: repository: registry.reshapr.io/reshapr/reshapr-ui tag: "1.0.0" pullPolicy: IfNotPresent podDisruptionBudget: enabled: true minAvailable: 1 controlPlane: url: http://reshapr-control-plane-ctrl.reshapr-system.svc.cluster.local:5555 publicUrl: https://app.reshapr.example.com apiKey: existingSecret: reshapr-web-ui-api-key key: api-key publicUrl: https://ui.reshapr.example.com ingress: enabled: true className: "" hosts: - host: ui.reshapr.example.com paths: - path: / pathType: Prefix tls: - secretName: reshapr-web-ui-tls hosts: - ui.reshapr.example.com ``` Install and verify it: ```bash helm upgrade --install reshapr-ui \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-web-ui \ --version 0.0.14 \ --namespace reshapr-system \ --values values/web-ui.yaml kubectl get pods --namespace reshapr-system \ --selector app.kubernetes.io/instance=reshapr-ui curl --fail --silent --head https://ui.reshapr.example.com ``` ## 5. Configure the controllers Pin controllers `0.0.3` when using charts `0.0.14`. Create `values/controllers.yaml`: ```yaml operator: enabled: true image: tag: "0.0.3" replicaCount: 1 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi admissionController: enabled: true image: tag: "0.0.3" replicaCount: 2 certificate: provider: cert-manager ``` Use `openshift` or `existing` instead when those certificate providers match your platform. The [controllers chart reference](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/controllers/README.md) documents all three modes. Chart `0.0.14` packages the controllers `0.0.3` CRDs for a fresh installation; follow the upgrade guide when CRDs from an older release already exist. Install the release: ```bash helm upgrade --install reshapr-controllers \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-controllers \ --version 0.0.14 \ --namespace reshapr-system \ --values values/controllers.yaml ``` Register the operator ServiceAccount with the control plane: ```bash export RESHAPR_ADMIN_API_KEY='' reshapr admin --server https://app.reshapr.example.com \ service-account create reshapr-system-operator \ --k8s-subject reshapr-system:reshapr-controllers-operator \ --allowed-organizations '["*"]' \ --validity-days 90 ``` Then verify the workloads and CRDs: ```bash kubectl get pods --namespace reshapr-system \ --selector app.kubernetes.io/instance=reshapr-controllers kubectl get crd | grep 'reshapr.io' ``` ## 6. Configure the proxy Create `values/proxy.yaml`: ```yaml replicaCount: 3 image: tag: "1.0.0" pullPolicy: IfNotPresent gateway: idPrefix: prod-gateway fqdns: mcp.reshapr.example.com labels: "env=production;region=eu-west-1;cluster=prod-01" controlPlane: host: reshapr-control-plane-ctrl.reshapr-system.svc.cluster.local port: 5555 existingSecret: reshapr-gateway-token tokenKey: token clustering: enabled: true networkPolicy: enabled: true autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80 podDisruptionBudget: enabled: false serviceMonitor: enabled: true additionalLabels: prometheus: kube-prometheus ingress: enabled: true className: "" hosts: - host: mcp.reshapr.example.com paths: - path: / pathType: Prefix tls: - secretName: reshapr-gateway-tls hosts: - mcp.reshapr.example.com ``` The chart encrypts JGroups traffic and can generate its clustering keystore. For controlled rotation and disaster recovery, provision `clustering.encryption.existingSecret` instead and manage the keystore lifecycle explicitly. The chart's NetworkPolicy covers JGroups clustering traffic only. It is not a complete namespace ingress or egress policy. `ServiceMonitor` requires the Prometheus Operator CRD; disable it when that API is unavailable. Install the release: ```bash helm upgrade --install reshapr-proxy \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-proxy \ --version 0.0.14 \ --namespace reshapr-proxies \ --values values/proxy.yaml ``` Verify its rollout, HPA, and cluster-facing health: ```bash kubectl rollout status deployment/reshapr-proxy \ --namespace reshapr-proxies --timeout 5m kubectl get hpa,pods,networkpolicy,servicemonitor \ --namespace reshapr-proxies curl --fail --silent https://mcp.reshapr.example.com/q/health/ready | jq ``` ## 7. Verify a functional MCP endpoint Create an endpoint with **[Your First GitOps-managed MCP Endpoint](../tutorials/first-gitops-mcp-endpoint.md)** or use an existing ready Exposition. Set its public URL: ```bash export MCP_URL='https://mcp.reshapr.example.com/mcp///' ``` Discover the MCP server through the production ingress: ```bash curl --fail --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-production-check","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result | {supportedVersions, capabilities}' ``` Health probes show that workloads can serve traffic. This MCP request additionally verifies DNS, ingress, proxy registration, Exposition propagation, and endpoint routing. Complete the check with a real `tools/call` for a non-destructive operation from your Service. ## Result The four charts are installed at `0.0.14`, the runtime workloads use `1.0.0`, the controllers use `0.0.3`, PostgreSQL and credentials are externally managed, public routes use TLS, and a production ingress answers a functional MCP request. ## Limits - Multiple replicas and PodDisruptionBudgets do not provide end-to-end availability by themselves. PostgreSQL, ingress, DNS, cluster capacity, and failure-domain placement remain part of the design. - The proxy HPA requires working resource metrics and tested scaling thresholds. Control-plane and Web UI autoscaling are not configured by these charts. - The operator remains a single replica in this example; the admission webhook has two replicas. - The generated clustering keystore is retained across Helm upgrades. Database encryption-key rotation is administrator-triggered; general credential rotation and backup are not automated. - The charts do not provide automated rollback or database backup and restore. - TLS terminates at ingress in this topology. Internal transport security depends on your cluster network and service-mesh policy. - The chart NetworkPolicy protects JGroups traffic only; define broader policies separately. - A `ServiceMonitor` only creates a scrape target. Alerting, retention, dashboards, and SLOs remain external responsibilities. ## Next step Use **[Observe the reShapr Proxy](./operations/observe-and-audit.md)** to connect the proxy to your telemetry pipeline. Use **[Upgrade reShapr and Rotate Runtime Secrets](./operations/upgrade-and-rotate.md)** to prepare the next maintenance window, or **[Manage reShapr Resources with GitOps](./manage-resources-with-gitops.md)** to operate endpoint desired state and cleanup. Use the release-tagged [chart READMEs and values](https://github.com/reshaprio/reshapr-helm-charts/tree/0.0.14) when adapting this bounded topology. --- ## Docker Compose Learn how to run reShapr locally using Docker Compose for development and testing purposes. :::tip Watch the setup The [11-second local Docker demo](https://youtu.be/ECZAiXbSwDc) previews the startup flow. Use this guide for version-pinned commands and verification. ::: ## Prerequisites Before you begin, make sure you have the following installed on your machine: - **[Docker](https://docs.docker.com/get-docker/)** with Docker Compose v2, or **[Podman](https://podman.io/)** with Compose support - **[Node.js](https://nodejs.org/)** 20 or later, required by the reShapr CLI - The **reShapr CLI** installed globally: ```bash npm install -g @reshapr/reshapr-cli --allow-scripts=@scarf/scarf ``` ## Quick start with the CLI The simplest way to run reShapr locally is through `reshapr run`. Pin the release so the downloaded Compose file, container images, and this guide use the same version: ```bash reshapr run --release 1.0.0 ``` The CLI downloads the release-owned [`docker-compose-all-in-one.yml`](https://github.com/reshaprio/reshapr/blob/1.0.0/install/docker-compose-all-in-one.yml), updates its reShapr image tags to `1.0.0`, caches it under `~/.reshapr/`, and starts the stack in the background. Without `--release`, the CLI resolves `latest` through GitHub Releases. Use an explicit release for a reproducible environment. Use `nightly` only when you deliberately want artifacts from the `main` branch: ```bash reshapr run --release nightly ``` The CLI auto-detects Docker or Podman. To select one explicitly, use `--engine`: ```bash reshapr run --release 1.0.0 --engine podman ``` Add the optional Web UI with `--ui`: ```bash reshapr run --release 1.0.0 --ui ``` The Web UI addon is downloaded from the same release and becomes available at `http://localhost:3333`. The compose files are cached at `~/.reshapr/docker-compose-.yml` and `~/.reshapr/docker-compose-ui-addon-.yml`. ## Check status Once the containers are running, verify their status: ```bash reshapr status ``` The output identifies the selected release and container engine, then reports the Compose service status. Names and timestamps depend on your local engine and are not stable identifiers. The control plane is available at **`http://localhost:5555`** and the MCP proxy at **`http://localhost:7777`**. ## Log in with the CLI With your user created, authenticate the CLI against your local control plane: ```bash reshapr login --server http://localhost:5555 ``` You'll be prompted for your username and password. Once authenticated: :::info The default username is `admin`, and the default password is `password`. ::: ```bash reshapr login --server http://localhost:5555 ``` ```bash ℹ️ Enter your credentials ✅ Login successful! ℹ️ Welcome, admin! ℹ️ Organization: reshapr ✅ Configuration saved to /Users/you/.reshapr/config ``` > From here, follow **[Your First MCP Endpoint, End to End](../tutorials/getting-started.md)** to import a versioned API contract, expose it, and call a Tool through the local proxy. ## Stop the containers When you're done, shut everything down: ```bash reshapr stop ``` This runs the selected engine's Compose `down` command on every saved compose file, including the Web UI addon when enabled, and cleans up the run state. ## Manual setup (without the CLI) If you prefer to manage Docker Compose directly, check out the same release used by this guide: ```bash git clone --branch 1.0.0 --depth 1 https://github.com/reshaprio/reshapr.git cd reshapr ``` Start all services (control plane, proxy, and database) at once: ```bash docker compose -f install/docker-compose-all-in-one.yml up -d ``` To include the Web UI, compose the addon with the base file: ```bash docker compose -f install/docker-compose-all-in-one.yml \ -f install/docker-compose-ui-addon.yml up -d ``` With Podman, replace `docker compose` with `podman compose` in these commands. ```bash podman compose -f install/docker-compose-all-in-one.yml up -d ``` ```bash docker compose -f install/docker-compose-all-in-one.yml down ``` ## Result `reshapr status` reports the Compose services as running, the control plane responds at `http://localhost:5555`, and the proxy accepts MCP requests at `http://localhost:7777`. ## Limits - This topology is intended for local development and testing, not production availability or durability. - Local credentials and ports in this guide use the release defaults; change them before exposing the stack beyond your machine. - Stopping the stack does not remove its named volumes. Manage local data lifecycle with your container engine. ## Next step - **[Your First MCP Endpoint, End to End](../tutorials/getting-started.md)** — import, expose, and call an API Tool - **[Helm Charts Overview](../references/helm-charts.md)** — choose a Kubernetes deployment topology - **[How it works](../overview/how-it-works.md)** — understand the reShapr architecture --- ## Import OpenAPI, GraphQL, or Protobuf Artifacts Import an API contract to create or update its reShapr Service and main Artifact. Choose the source method and metadata according to the contract format and its dependencies. ## Prerequisites - Access to a reShapr `1.0.0` environment - Permission to import Artifacts in the current organization - For CLI procedures, the `reshapr` CLI authenticated against that environment - For remote sources, network access from the control plane to the source URL ## Choose the format and source | Contract | Recognition in `1.0.0` | Default Service identity | Recommended source | |---|---|---|---| | OpenAPI | An `openapi: 3...` field in JSON or YAML | `info.title` and `info.version` | URL when the document has external `$ref` values | | GraphQL | A schema, query, mutation, or `# microcksId:` declaration | None; provide name and version | File or URL | | Protobuf | A `syntax = "proto3";` declaration | Full service name and package-derived version | URL when the root file imports other files | Swagger and OpenAPI 2.x documents are not supported. Convert them to OpenAPI 3 before importing them. The Web UI accepts `.json`, `.yaml`, `.yml`, `.graphql`, `.graphqls`, `.gql`, and `.proto` files. Format recognition is based on the document content, not only its extension. ## Import from the Web UI Open the Service import action or the **Import** stage of **Quick Start**, then choose one of these sources: - **Local file** uploads one contract from your computer. - **Remote URL** asks the control plane to retrieve the contract and gives the importer a base URL for resolving dependencies. For a protected URL, select an existing Secret of type `ARTIFACT`. The control plane uses that Secret only to retrieve the Artifact source. It is not the credential used by a Gateway to call the backend API. Complete **Service name override** and **Service version override** together when the contract cannot provide the intended identity. Both fields are mandatory for GraphQL. They are optional overrides for OpenAPI and Protobuf. After import, open the resulting Service and confirm its name, version, protocol type, operations, and main Artifact. Importing the same Service name and version again updates that Service; changing either value creates a different Service identity. ## Import OpenAPI 3 Import the immutable Open-Meteo OpenAPI 3 contract from its URL: ```bash reshapr import \ --url https://raw.githubusercontent.com/open-meteo/open-meteo/1.5.6/openapi/forecast.yml ``` The command should report a discovered Service named **Open-Meteo Weather Forecast API**, version `1.0`. Generated Service identifiers differ between environments. To import a self-contained local document instead, use: ```bash reshapr import --file ./openapi.yaml ``` For OpenAPI documents with external `$ref` values, prefer `--url`. The importer can then resolve relative and absolute references from the remote source. A local upload contains only the selected file and does not provide sibling files to the control plane. ## Import GraphQL GraphQL schemas do not define a reShapr Service name or version. Supply both values explicitly. Create a small local schema: ```bash cat > inventory.graphql <<'GRAPHQL' schema { query: Query } type Query { product(sku: ID!): Product } type Product { sku: ID! name: String! } GRAPHQL ``` Import it with its Service identity: ```bash reshapr import --file ./inventory.graphql \ --serviceName Inventory \ --serviceVersion 1.0 ``` The resulting Service should be named **Inventory**, version `1.0`, with an operation named `product` whose method is `QUERY`. The same metadata is required for a remote schema: ```bash reshapr import --url https://example.com/schema.graphql \ --serviceName Inventory \ --serviceVersion 1.0 ``` The URL in this last command is an example placeholder; replace it with a reachable schema URL. ## Import Protobuf 3 Create a self-contained proto3 contract: ```bash cat > inventory.proto <<'PROTO' syntax = "proto3"; package example.inventory.v1; service InventoryService { rpc GetProduct(GetProductRequest) returns (Product); } message GetProductRequest { string sku = 1; } message Product { string sku = 1; string name = 2; } PROTO ``` Import the contract: ```bash reshapr import --file ./inventory.proto ``` The importer compiles the proto3 document and derives the Service name from the service descriptor. In this example, the package suffix supplies version `v1`. Use `--serviceName` and `--serviceVersion` together to override that identity when needed. ### Resolve Protobuf imports If the root document contains declarations such as: ```protobuf ``` publish the root document and its dependencies under a common, reachable URL hierarchy, then import the root by URL: ```bash reshapr import \ --url https://example.com/protos/example/inventory/v1/inventory.proto ``` The URL above is an example placeholder. The control plane resolves imports relative to the remote source while compiling the descriptor. Well-known Protobuf types bundled with the compiler do not need to be published separately. Uploading only the root `.proto` file does not upload sibling imports. Use a self-contained file or a remote source when dependencies are required. ## Import from a protected URL Create or select an `ARTIFACT` Secret, then reference its name during URL import: ```bash reshapr import \ --url https://artifacts.example.com/contracts/inventory.yaml \ --secret artifact-registry ``` Both the URL and Secret name in this command are examples. The Secret must already exist in the current organization and contain credentials accepted by the source server. In the Web UI, choose the same Secret in the optional **Secret** field on the **Remote URL** tab. ## Diagnose import failures | Symptom | Likely cause | Action | |---|---|---| | The format is not recognized | The declaration is absent, appears in an unsupported form, or the document is OpenAPI 2.x | Confirm `openapi: 3...`, `syntax = "proto3";`, or a recognizable GraphQL declaration; convert OpenAPI 2.x | | GraphQL import reports missing Service metadata | Name or version was omitted | Supply both Service name and Service version | | OpenAPI import fails on an external `$ref` | A local upload has no remote resolution context, or the referenced URL is unavailable | Import the root document by URL and verify every reference from the control-plane network | | Protobuf parsing reports a missing dependency | An imported `.proto` file could not be resolved or compiled | Publish the dependency at the expected relative URL, then import the root by URL | | URL import returns an authentication or retrieval error | The control plane cannot reach the URL, or the selected Secret is missing or invalid | Test reachability from the control-plane environment and verify the `ARTIFACT` Secret | | Import succeeds with an unexpected Service identity | The contract metadata or package-derived version differs from the intended identity | Re-import with name and version overrides; remember that a new identity creates a separate Service | Use `reshapr service list` and `reshapr service get ` to inspect the result. For complete command options, see **[CLI Commands](../references/cli-commands.md)**. ## Next steps - Continue from an imported OpenAPI Service to an MCP endpoint with **[Web UI Quickstart: From Import to Exposition](../tutorials/web-ui-quickstart.md)**. - Learn how main and attached Artifacts differ in **[Services and Artifacts](../explanations/services-and-artifacts.md)**. - Add Prompts, Resources, Custom Tools, or output filters with **[Attach and Select reShapr Artifacts](./select-reshapr-artifacts.md)**. ## Evidence and limits This guide was last verified with reShapr `1.0.0` on 2026-09-18. Format recognition follows the release-tagged **[Artifact importer factory](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/artifacts/ArtifactImporterFactory.java)**. Metadata and dependency behavior follow the **[GraphQL importer](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/artifacts/GraphQLImporter.java)**, **[Protobuf importer](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/artifacts/ProtobufImporter.java)**, and **[OpenAPI importer](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/artifacts/OpenAPIImporter.java)**. Import validates and models a contract; it does not prove that the backend endpoint is reachable or that its runtime behavior matches the contract. Verify those properties after creating a Plan and Exposition. --- ## Manage reShapr Resources with GitOps Use this guide to manage reShapr custom resources as desired state without coupling the repository to Flux, Argo CD, or another reconciliation engine. ## Prerequisites - A Kubernetes cluster with reShapr controllers `0.0.3` installed - A registered operator identity with access to the target reShapr `1.0.0` organization - `kubectl` access to the application and `reshapr-system` namespaces - A Git repository reconciled to the cluster - A completed **[first GitOps-managed MCP endpoint](../tutorials/first-gitops-mcp-endpoint.md)** or equivalent Service, ConfigurationPlan, and Exposition The [controllers documentation at `0.0.3`](https://github.com/reshaprio/reshapr-controllers/tree/0.0.3/documentation) owns the complete CRD behavior. This guide focuses on repository structure and lifecycle decisions. ## Organize resources by dependency Keep shared platform objects separate from endpoint-specific resources, and make dependencies visible in file names or reconciliation units: ```text environments/ └── production/ ├── platform/ │ ├── 10-secret-sources.yaml │ └── 20-gateway-groups.yaml └── endpoints/ └── open-meteo/ ├── 10-service.yaml ├── 20-custom-tools.yaml ├── 21-resources.yaml ├── 30-configuration-plan.yaml └── 40-exposition.yaml ``` :::tip This layout is a suggestion, not a requirement. Organize the resources according to your existing repository structure, naming conventions, and GitOps practices; what matters is that dependencies and reconciliation order remain understandable. ::: Reconcile a Service and GatewayGroup before the objects that refer to them. Reconcile the Exposition last because it requires a ready Service, ConfigurationPlan, and GatewayGroup in the control plane. File names alone do not make every GitOps engine wait for readiness. When your engine supports health checks or dependencies, define separate reconciliation units and require the dependencies to become ready before applying the Exposition. ## Keep credentials out of custom resources Do not commit tokens or passwords directly in a `SecretSource`. Commit only a reference to a Kubernetes Secret in the same namespace: ```yaml apiVersion: reshapr.io/v1alpha1 kind: SecretSource metadata: name: backend-credentials namespace: production annotations: reshapr.io/instance: reshapr-control-plane-ctrl.reshapr-system reshapr.io/organization: reshapr spec: secrets: - name: weather-backend-token description: Token used to call the weather backend type: ENDPOINT valuesFrom: secretRef: weather-backend-credentials tokenKey: token tokenHeaderKey: token-header ``` Create the referenced Kubernetes Secret with your secret-management system. The operator requires its separate Secret-reader RBAC to read it. The [`SecretSource` reference](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/secretsource-cr.md) lists the supported keys and cleanup behavior. Check each synchronized entry rather than relying only on the aggregate state: ```bash kubectl get secretsource backend-credentials \ --namespace production \ --output json | jq '{status: .status.status, observed: .status.observedGeneration, conditions: .status.conditions}' ``` ## Observe reconciliation correctly `Service`, `GatewayGroup`, `ConfigurationPlan`, `Exposition`, and `SecretSource` report their phase in `status.status`. A successful reconciliation sets `status.observedGeneration` to the current `metadata.generation`: ```bash kubectl get services.reshapr.io,gatewaygroups.reshapr.io,configurationplans.reshapr.io,expositions.reshapr.io,secretsources.reshapr.io \ --namespace production \ --output json | jq -r ' .items[] | [.kind, .metadata.name, .status.status, .metadata.generation, .status.observedGeneration, (.status.message // "")] | @tsv' ``` Treat a resource as reconciled only when its phase is `READY` and the two generations match. `ERROR` indicates a rejected or unresolved desired state; use `status.message` and the operator logs to diagnose it. `IN_PROGRESS`, `UNKNOWN`, and `PREEXISTING` are not equivalent to `READY`. `CustomTools` and `Resources` use `status.state` and do not expose `observedGeneration` in controllers `0.0.3`: ```bash kubectl get customtools.reshapr.io,resources.reshapr.io \ --namespace production \ --output json | jq -r ' .items[] | [.kind, .metadata.name, .status.state, (.status.message // "")] | @tsv' ``` Against reShapr `1.0.0`, valid `CustomTools` and `Resources` custom resources can each reach `READY`. Controllers `0.0.3` currently upload both artifact kinds with the same `artifact.json` filename, however, so reconciling both kinds against one Service can replace the previously attached artifact. Use only one of these CR kinds per Service, or manage the additional artifact through another supported reShapr interface. ## Update a ConfigurationPlan through Git Add a request-header policy to the tracked ConfigurationPlan. This example keeps two incoming headers, drops one explicitly, and renames one before the backend call: ```yaml spec: includedOperations: - GET /v1/forecast headerPolicy: request: allow: - X-Request-Id - X-Client-Id deny: - X-Internal-Debug rename: - from: X-Client-Id to: X-Consumer-Id ``` Review the Kubernetes diff: ```bash kubectl diff --filename environments/production/endpoints/open-meteo ``` Commit and push the change: ```bash git add environments/production/endpoints/open-meteo/30-configuration-plan.yaml git commit -m 'Configure Open-Meteo request headers' git push ``` Wait for your GitOps controller to apply the commit. Then confirm that the ConfigurationPlan has observed its new generation: ```bash kubectl get configurationplan open-meteo-gitops-configurationplan \ --namespace production \ --output jsonpath='{.status.status}{" generation="}{.metadata.generation}{" observed="}{.status.observedGeneration}{"\n"}' ``` Finish with a real call to the affected MCP endpoint. Kubernetes readiness proves reconciliation, not backend reachability or a successful Tool response. ## Remove resources without leaving surprises Delete dependants before their dependencies: 1. Expositions 2. ConfigurationPlans 3. CustomTools and Resources 4. Services 5. GatewayGroups that are no longer shared 6. SecretSources that are no longer referenced Remove the corresponding files in one reviewable change, or split the removal into ordered reconciliation units when your GitOps engine cannot guarantee deletion order. For `Service`, `GatewayGroup`, `Exposition`, and `SecretSource`, `spec.keepOnDelete` defaults to `false`: deleting the custom resource also asks the operator to delete the corresponding remote object. Set it to `true` before removal only when the remote object must intentionally outlive Kubernetes management. ConfigurationPlans are cleaned up remotely by their reconciler and do not expose `keepOnDelete` in controllers `0.0.3`. Deleting a `CustomTools` or [`Resources`](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/resources-cr.md) custom resource does **not** remove its remote Artifact in controllers `0.0.3`. If the parent Service is retained, remove that Artifact through a supported reShapr interface or record it as intentionally unmanaged. Deleting the parent Service with `keepOnDelete: false` removes the Service that contains those Artifacts. Do not remove CRDs as part of an application cleanup. Deleting a CRD deletes every custom resource of that kind across the cluster. ## Result The repository expresses resource dependencies, sensitive values remain outside tracked custom resources, status checks match each CRD's actual contract, and updates or removals have an explicit verification and cleanup path. ## Limits - This guide does not configure a GitOps engine or prescribe its dependency and health-check syntax. - Controllers `0.0.3` do not expose a uniform readiness contract across all seven custom resources. - `CustomTools` and `Resources` currently use the same remote artifact filename and can replace each other when reconciled for one Service. - `CustomTools` and `Resources` deletion can leave remote Artifacts when their parent Service remains. - `keepOnDelete` preserves remote state but does not transfer that state to another Kubernetes resource. - A successful reconciliation does not test ingress, proxy health, backend authentication, or Tool execution. ## Next step Use **[Deploy reShapr on Kubernetes for Production](./deploy-kubernetes-production.md)** to turn the surrounding platform into a production-oriented topology, or review **[Kubernetes APIs and Controllers](../references/kubernetes-apis.md)** for the owner of every CRD contract. --- ## Attach and Select reShapr Artifacts Attach reusable agent-oriented capabilities to one Service, then use `includedArtifacts` to decide which capabilities each Configuration Plan exposes. ## Prerequisites - The Open-Meteo Service from **[Your First MCP Endpoint](../tutorials/getting-started.md)** - reShapr CLI `1.0.0`, authenticated against the target environment - `curl` and `jq` - A running proxy registered as a Gateway in Gateway Group `1` Locate the Service: ```bash RESHAPR_SERVICE_ID="$( reshapr service list --output json \ | jq -er 'map(select(.name == "Open-Meteo Weather Forecast API" and .version == "1.0")) | first | .id' )" export RESHAPR_SERVICE_ID ``` ## Attach the four Artifact types Attach a Prompt, Resource, declarative Custom Tool, and Tool output filter. Structured output provides the exact Artifact names used by Plan selection. ```bash PROMPT_ARTIFACT_JSON="$(reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-weather-prompt.yaml' \ --output json)" PROMPT_ARTIFACT_NAME="$(jq -er '.name' <<<"$PROMPT_ARTIFACT_JSON")" PROMPT_ARTIFACT_ID="$(jq -er '.id' <<<"$PROMPT_ARTIFACT_JSON")" RESOURCE_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-weather-resource.yaml' \ --output json \ | jq -er '.name' )" CUSTOM_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-current-weather.yaml' \ --output json \ | jq -er '.name' )" FILTER_ARTIFACT_NAME="$( reshapr attach \ --url 'https://reshapr.io/examples/context-control/open-meteo-current-weather-filter.yaml' \ --output json \ | jq -er '.name' )" export PROMPT_ARTIFACT_NAME PROMPT_ARTIFACT_ID RESOURCE_ARTIFACT_NAME export CUSTOM_ARTIFACT_NAME FILTER_ARTIFACT_NAME unset PROMPT_ARTIFACT_JSON ``` Attaching a file again with the same source updates it instead of creating another copy. ## Inspect derived capabilities ```bash reshapr artifact list --serviceId "$RESHAPR_SERVICE_ID" ``` The `CAPS` column reports how many capabilities each Artifact declares. Get one Artifact by ID to see its names: ```bash reshapr artifact get '' ``` For these samples, the derived capabilities are: | Artifact kind | Capability | |---|---| | `Prompts` | `weather_brief` | | `Resources` | `reshapr://open-meteo/weather-code-note` | | `CustomTools` | `current_weather` | | `ToolsOutputFilters` | `current_weather` | Capabilities are read-only composition metadata extracted when an Artifact is attached or updated. They identify declarations but do not replace MCP discovery or authorization. ## Create two Artifact selections Create an **action Plan** containing the Custom Tool and Prompt, and a **context Plan** containing the same Tool plus the Resource and output filter. ```bash ACTION_ARTIFACTS="$( jq -cn \ --arg custom "$CUSTOM_ARTIFACT_NAME" \ --arg prompt "$PROMPT_ARTIFACT_NAME" \ '[$custom, $prompt]' )" CONTEXT_ARTIFACTS="$( jq -cn \ --arg custom "$CUSTOM_ARTIFACT_NAME" \ --arg resource "$RESOURCE_ARTIFACT_NAME" \ --arg filter "$FILTER_ARTIFACT_NAME" \ '[$custom, $resource, $filter]' )" ACTION_PLAN_ID="$( reshapr config create 'weather-action-with-prompt' \ --serviceId "$RESHAPR_SERVICE_ID" \ --backendEndpoint 'https://api.open-meteo.com' \ --includedOperations '["GET /v1/forecast"]' \ --includedArtifacts "$ACTION_ARTIFACTS" \ --output json \ | jq -er '.id' )" CONTEXT_PLAN_ID="$( reshapr config create 'weather-action-with-context' \ --serviceId "$RESHAPR_SERVICE_ID" \ --backendEndpoint 'https://api.open-meteo.com' \ --includedOperations '["GET /v1/forecast"]' \ --includedArtifacts "$CONTEXT_ARTIFACTS" \ --output json \ | jq -er '.id' )" export ACTION_PLAN_ID CONTEXT_PLAN_ID unset ACTION_ARTIFACTS CONTEXT_ARTIFACTS ``` `includedArtifacts` contains Artifact **names**, not IDs. If it is absent or empty, all attached Artifacts apply. `includedOperations` uses canonical Service operation names; the selected `GET /v1/forecast` operation is then replaced by the `current_weather` MCP Tool declared in the Custom Tool Artifact. ## Expose the selections Use `https` for reShapr Online or `http` for the local Compose Gateway: ```bash export MCP_SCHEME='https' ACTION_MCP_URL="${MCP_SCHEME}://$( reshapr expo create \ --configuration "$ACTION_PLAN_ID" \ --gateway-group 1 \ --name 'weather-action-with-prompt' \ --output json \ | jq -er '.endpoints[0]' )" CONTEXT_MCP_URL="${MCP_SCHEME}://$( reshapr expo create \ --configuration "$CONTEXT_PLAN_ID" \ --gateway-group 1 \ --name 'weather-action-with-context' \ --output json \ | jq -er '.endpoints[0]' )" export ACTION_MCP_URL CONTEXT_MCP_URL ``` ## Compare the exposed capabilities Use one helper for `tools/list`, `prompts/list`, and `resources/list`: ```bash list_capabilities() { local endpoint="$1" local method="$2" local result_key="$3" local request request="$(jq -cn --arg method "$method" '{ jsonrpc: "2.0", id: 1, method: $method, params: { _meta: { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": {name: "reshapr-docs", version: "1.0.0"}, "io.modelcontextprotocol/clientCapabilities": {} } } }')" curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header "Mcp-Method: $method" \ --data "$request" \ "$endpoint" | jq -r --arg key "$result_key" '.result[$key][] | .name // .uri' } ``` Compare the two Expositions: ```bash list_capabilities "$ACTION_MCP_URL" tools/list tools list_capabilities "$ACTION_MCP_URL" prompts/list prompts list_capabilities "$ACTION_MCP_URL" resources/list resources list_capabilities "$CONTEXT_MCP_URL" tools/list tools list_capabilities "$CONTEXT_MCP_URL" prompts/list prompts list_capabilities "$CONTEXT_MCP_URL" resources/list resources ``` Both list `current_weather`. Only the action Exposition lists `weather_brief`; only the context Exposition lists `weather-code-note`. The context Plan also filters `current_weather` results, as demonstrated in **[Context Control in Practice](../tutorials/context-control-in-practice.md)**. ## Preview deletion impact Ask the CLI to delete the Prompt Artifact: ```bash reshapr artifact delete "$PROMPT_ARTIFACT_ID" ``` Before deleting, the CLI lists the Configuration Plans that reference the Artifact and asks for confirmation. The action Plan appears in the impact preview; the context Plan does not. Answer `n` to retain the Artifact and the two capability surfaces built by this guide. Confirming deletion would remove the Artifact name from affected Plans and propagate their updates to the corresponding Expositions. If the removed name was a Plan's only selection, the resulting empty list means **all remaining attached Artifacts apply**. Adjust or delete that Plan first when this fallback would broaden its MCP surface. ## Result One Service now has four reusable attached Artifact types and two Configuration Plans that expose observably different Prompt, Resource, and response-treatment capabilities. ## Limits - Artifact capabilities describe declared names; use MCP list methods to verify what an Exposition serves. - Artifact selection is Plan-wide, not conditional per user or per Tool call. - Output filters are not an authorization boundary and fail open in reShapr `1.0.0`. - The deletion step previews impact and is deliberately cancelled; confirming it changes the action Plan. - Re-running the commands with the same Plan or Exposition names requires deleting or renaming the previous resources. ## Next step - **[Build a Scripted Custom Tool](./build-scripted-custom-tool.md)** composes same-Service and cross-Service calls behind one action. - **[Context Control in Practice](../tutorials/context-control-in-practice.md)** measures a filtered Tool result. - **[Services and Artifacts](../explanations/services-and-artifacts.md)** explains main, attached, and derived capabilities. - **[Context Control](../explanations/context-control.md)** compares the available mechanisms and trade-offs. --- ## Test an MCP Endpoint with an MCP Client Use these requests to validate an MCP endpoint before connecting it to an agent. The examples use `curl` so that the HTTP exchange remains visible. ## Prerequisites - A reachable reShapr MCP endpoint - `curl` and `jq` - An API key or bearer token when the endpoint is protected - The name and valid arguments of one Tool exposed by the endpoint Set the endpoint returned by the Exposition: ```bash export MCP_URL='https:///mcp/' ``` ## Choose a protocol mode | Mode | Protocol | First request | State carried by the client | |---|---|---|---| | Stateless | `2026-07-28` | `server/discover` | Protocol metadata on every request; no session ID | | Session-based | `2025-11-25` or earlier | `initialize` | Protocol version and the returned `MCP-Session-Id` | Use one mode consistently. Do not send a legacy session ID with a stateless request. See **[MCP Compatibility: Session and Stateless Modes](../explanations/mcp-compatibility.md)** for negotiation, state, dialect, and elicitation differences. The **[MCP Support Matrix](../references/mcp-support.md)** lists exact version and method support. ## Test the stateless protocol ### Discover the server ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq . ``` The response should identify the server and include `2026-07-28` among its supported versions. ### List Tools ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/list' \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result.tools[] | {name, description}' ``` ### Call a Tool Replace the Tool name and arguments with values returned by `tools/list`: ```bash export MCP_TOOL='get_v1_forecast' curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header "Mcp-Name: $MCP_TOOL" \ --data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_v1_forecast","arguments":{"latitude":"48.8566","longitude":"2.3522","current":["temperature_2m","weather_code","wind_speed_10m"],"timezone":"Europe/Paris"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq . ``` The `Mcp-Method`, `Mcp-Name`, and `MCP-Protocol-Version` headers must agree with the request body when supplied. ## Test a session-based protocol Initialize the session and capture the response headers: ```bash export MCP_HEADERS="$(mktemp)" curl --silent --show-error --dump-header "$MCP_HEADERS" \ --header 'Content-Type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"reshapr-curl","version":"1.0.0"}}}' \ "$MCP_URL" | jq . export MCP_SESSION_ID="$(awk 'tolower($1) == "mcp-session-id:" {print $2}' "$MCP_HEADERS" | tr -d '\r')" test -n "$MCP_SESSION_ID" ``` Send both negotiated headers on later requests: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2025-11-25' \ --header "MCP-Session-Id: $MCP_SESSION_ID" \ --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \ "$MCP_URL" | jq '.result.tools[] | {name, description}' ``` Reuse the same two headers for `tools/call`. If the session is lost or expires, initialize a new one. ## Add endpoint authentication For an endpoint protected by a reShapr API key, add: ```bash --header "x-reshapr-key: $RESHAPR_API_KEY" ``` For an endpoint protected by OAuth 2.0, add: ```bash --header "Authorization: Bearer $ACCESS_TOKEN" ``` These headers protect access to the MCP endpoint. Backend authentication is configured separately by the Configuration Plan. ## Diagnose a failed request | Symptom | Likely cause | Check | |---|---|---| | HTTP `400` | Invalid JSON-RPC request, unsupported version, or modern mirror-header mismatch | Compare the method, target name, protocol header, and `_meta` values | | HTTP `401` | Missing or invalid endpoint credentials | Add the configured API key or bearer token | | HTTP `403` | Authenticated token lacks an accepted issuer or required Exposition scope | Inspect the OAuth configuration and token claims | | HTTP `404` | Unknown Exposition or method unavailable in the selected protocol | Check the endpoint URL and protocol mode | | JSON-RPC `error` | The MCP request reached the server but could not be processed | Read `error.code`, `error.message`, and `error.data` | | `result.isError: true` | The Tool ran but the backend or Tool execution failed | Inspect `result.content` and proxy logs | Use `curl --include` when you need to inspect the HTTP status and response headers together. ## Result The endpoint is ready for an MCP client when negotiation succeeds, `tools/list` returns the expected Tool, and `tools/call` returns content without a JSON-RPC error or `result.isError: true`. ## Limits - This guide validates Streamable HTTP endpoints; reShapr does not expose an MCP WebSocket transport. - Supported protocol details evolve. Use the protocol mode implemented by the client you intend to connect. - A successful Tool call validates the selected endpoint and backend operation, not every Tool in the Exposition. ## Next step - **[Review MCP compatibility](../explanations/mcp-compatibility.md)** before integrating a client that supports several protocol versions. - **[Create your first MCP endpoint](../tutorials/getting-started.md)** if you do not have one yet. - **[Protect an MCP endpoint with an API key](./security/api-key.md)** before sharing an unprotected endpoint. - **[Protect an MCP Endpoint with OAuth 2.0](./security/oauth.md)** when clients need authenticated identity and scopes. - **[Security Capabilities and Limits](../explanations/security-model.md)** explains the client-to-proxy and proxy-to-backend trust boundaries. --- ## From API Contract to Agent Action An API operation and an MCP Tool are related, but they are not the same contract. reShapr translates backend-oriented definitions into MCP capabilities, then lets a Configuration Plan select and reshape what an agent can use. ## The transformation path ```mermaid sequenceDiagram participant Owner as API owner participant Control as Control plane participant Proxy as reShapr proxy participant Client as MCP client participant API as Backend API Owner->>Control: Import API Artifact Control->>Control: Discover Service and operations Owner->>Control: Attach reShapr Artifacts Owner->>Control: Create Plan and Exposition Control-->>Proxy: Synchronize selected configuration Client->>Proxy: tools/list Proxy-->>Client: Selected generated and custom Tools Client->>Proxy: tools/call with arguments Proxy->>API: Protocol-specific backend request API-->>Proxy: Backend response Proxy->>Proxy: Apply selected output filter and encoding Proxy-->>Client: MCP Tool result ``` ## 1. Import discovers the backend contract The main API Artifact provides operations and schemas. reShapr discovers a versioned Service from an OpenAPI document, GraphQL schema, or Protocol Buffer definition. This stage describes what the backend can do; it does not yet decide what a particular agent should see. ## 2. Attached Artifacts add agent-oriented capabilities Prompts, Resources, Custom Tools, and output filters enrich the Service without changing the backend contract. A declarative Custom Tool can present a more intentional name, description, and input schema over a generated operation. A scripted Custom Tool can orchestrate several calls, but it introduces code and should be chosen only when declarative mapping is insufficient. ## 3. A Plan selects the MCP surface The Configuration Plan determines which Service operations and attached Artifacts apply. Operation selection controls generated Tools. Artifact selection controls additional Prompts, Resources, Custom Tools, and filters. This allows two Plans for the same Service to expose different contracts. The backend API remains unchanged, while each MCP client receives only the surface selected by its Exposition. ## 4. `tools/list` describes the selected contract When a client lists Tools, the proxy converts selected Service operations into MCP Tool schemas and includes selected Custom Tools. Tool names, descriptions, and input schemas are context sent to the client; reducing this list reduces the contract the client must inspect. The Tool list is not an authorization decision by itself. Endpoint authentication protects the Exposition, and backend credentials protect the backend boundary. reShapr does not apply different OAuth scopes to individual Tools in a single Exposition. ## 5. `tools/call` dispatches to the backend The proxy validates the requested Tool and arguments, maps the call to the REST, GraphQL, or gRPC backend contract, and applies configured backend credentials. A declarative Custom Tool still targets a generated operation; its stable agent-facing contract can remain unchanged while mapping details evolve with the Plan and Artifact. ## 6. Response treatment happens before MCP delivery A selected output filter can retain JSON branches, apply JSON Patch operations, remove recursively empty values, and encode the resulting data as TOON. For the `1.0.0` baseline, these stages run in that order: ```text backend JSON -> jsonRetain -> jsonPatches -> compact (optional) -> TOON (optional) -> MCP Tool result ``` Filtering changes the content returned by a call. TOON changes its encoding. Neither mechanism reduces the number of Tools advertised by `tools/list`; operation and Artifact selection do that earlier. ## Contracts and data cross different boundaries | Flow | Content | Controlled by | |---|---|---| | Control plane to proxy | Exposition configuration and selected Artifacts | Organization, Plan, and Gateway Group | | MCP client to proxy | Tool discovery and calls | Exposition authentication and MCP protocol | | Proxy to backend | Translated API request | Backend endpoint and Secret | | Proxy to MCP client | Tool result after selected treatment | Output filter and encoding | Protecting one boundary does not protect another. See **[Security Capabilities and Limits](./security-model.md)** for the separation between endpoint and backend credentials. ## What makes an action agent-oriented An agent-oriented action is not merely a renamed endpoint. It has a bounded purpose, an understandable description, a small input contract, and a response containing what the caller needs. Context Control provides several mechanisms for reaching that result; **[Context Control: Mechanisms and Trade-offs](./context-control.md)** explains how to choose among them. --- ## Configuration Plan & Exposition Before being turned into an MCP Server endpoint, a reShapr **[Service](services-and-artifacts.md)** must have a *Configuration Plan* defined. A Configuration Plan will allow you to define how your Service will be consumed by MCP Clients. You’ll define the **backend endpoint** the reShapr proxy will target as well as the **security options** you want to apply to future expositions. In a nutshell, a Configuration Plan will allow you to define: - The backend endpoint URL of the existing service or API implementation you’re targeting, - The list of the Service operations you’d like to expose - you can choose, for example, to restrict access to read-only operations only, or to hide non-relevant operations, - The attached Prompts, Resources, Custom Tools, and output filters to include. When no artifact is selected, all artifacts attached to the Service are included, - The client cache policy for MCP `2026-07-28`, when clients support the corresponding **[modern response dialect](./mcp-compatibility.md#dialects-change-the-result-shape)**, - Whether the proxy emits audit events for calls made through the Plan, - The **[security options](security-model.md)** you’d like to enable for securing the access of the MCP endpoints - you can choose to secure access with an API key or using an OAuth Authorization Server, - The OAuth scopes required to access an Exposition. These scopes protect the Exposition as a whole; reShapr does not apply different OAuth scopes or claims to individual tools, Resources, or Prompts, - The accepted OAuth token audiences. By default, the requested Exposition URL is required; static audiences can be added, or validation can be disabled explicitly for compatibility, - The request headers forwarded to HTTP backends or converted to gRPC call metadata, using allow, deny, and rename rules. Hop-by-hop, reShapr authentication, and MCP transport headers are always removed, - The **[credentials Secret](services-and-artifacts.md)** the MCP Server will present to authorize access to the backend endpoint. The `headerPolicy.response` shape is present in the API and Kubernetes CRD but is reserved in `1.0.0`; only request headers are filtered or renamed. An absent request policy passes ordinary headers while removing `Authorization` and `Cookie` by default. Use explicit rules when a backend requires either header, and treat forwarding `Authorization` as a deliberate trust-boundary decision. A reShapr Service can have multiple Configuration Plans that match different environments or lifecycle stages. A Configuration Plan is always associated with a specific version of a Service and has to be replicated for other versions. Creating a Configuration Plan is not enough for your MCP Server to be ready and usable by your MCP Client. To do so, you must expose - ie **create an Exposition** - of your Configuration Plan. This last step before consuming a reShapr MCP Server endpoint is a simple declaration that allows you to target a Gateway Group. Creating an exposition is a simple operation that associates a Configuration Plan to a Gateway Group. Running proxies register logical Gateways, whose labels determine which groups and Expositions they match. See **[Gateway Groups, Gateways, and Proxies](gateway-groups-and-gateways.md)** for that distinction. Use **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)** to create two Plans for one Service and verify how `includedArtifacts` changes their MCP capability surfaces. --- ## Context Control: Mechanisms and Trade-offs Context Control is the deliberate design of the MCP contract and Tool results presented to a model. The goal is not simply to minimize bytes. It is to expose the smallest useful action surface while preserving the information required for a correct task. ## The mechanisms act at different stages ```mermaid flowchart LR API[API operations] --> Ops[Operation selection] Artifacts[Attached Artifacts] --> Select[Artifact selection] Ops --> Contract[MCP capability contract] Select --> Contract Contract --> Call[Tool call] Call --> Response[Backend response] Response --> Retain[Retain JSON branches] Retain --> Patch[Apply JSON patches] Patch --> Compact[Remove empty values] Compact --> Toon[Encode as TOON] Toon --> Result[MCP Tool result] ``` | Mechanism | Changes | Best used when | Trade-off | |---|---|---|---| | **Included or excluded operations** | Generated Tools visible from the Service | The backend has operations irrelevant to the consumer | Couples the Plan to backend operation names | | **`includedArtifacts`** | Additional Prompts, Resources, Custom Tools, and output filters | Consumers need different capability bundles from one Service | An empty selection means all attached Artifacts, so deletion requires care | | **Declarative Custom Tool** | Tool name, description, input schema, and mapping to one generated Tool | A backend operation needs a stable, agent-oriented contract | Mapping must be maintained when the target operation changes | | **Scripted Custom Tool** | A composed action that can invoke allowed Tools | One action requires orchestration or conditional logic | Adds code, runtime limits, and a larger maintenance surface | | **`jsonRetain`** | Branches present in a JSON Tool result | The backend returns useful data mixed with large irrelevant branches | Required fields can be removed accidentally | | **`jsonPatches`** | Shape or values of a JSON Tool result | The client needs a stable response shape or small transformation | Patch paths depend on the backend response structure | | **`compact`** | Recursively empty values in a JSON Tool result | Nulls and empty strings or containers add noise without meaning | Empty values that carry domain meaning are removed | | **TOON** | Encoding of the treated result | Repetitive structured data benefits from a more compact representation | The client or model must interpret TOON; semantics are unchanged | ## Reduce the advertised operation surface Start with operation selection when the problem is too many generated Tools. An allowlist is usually easier to review than a denylist because every exposed operation is explicit. In reShapr, `includedOperations` takes precedence when both lists are present; with neither list, all Service operations are available. This choice affects Tool discovery, not backend permissions. The backend should still enforce its own authorization policy. ## Replace protocol detail with a business action Use a declarative Custom Tool when the useful action maps to one generated Tool but needs a clearer contract. Use a scripted Custom Tool only when the action must combine calls or perform logic that declarative argument mapping cannot express. Custom Tools do not automatically make an operation safer or its result smaller. Pair them with operation selection and output filtering according to the actual task. ## Select capability bundles per consumer Attach reusable Artifacts to the Service, then use `includedArtifacts` to select them per Plan. This supports, for example, one Plan with only a weather Tool and another with the same Tool plus Prompts and Resources. Selection uses Artifact names, while derived capabilities show what each custom Artifact declares: - Prompt names for a Prompts Artifact; - Tool names for a Custom Tools Artifact; - Resource and Resource Template URIs for a Resources Artifact; - target Tool names for an output-filter Artifact. These capability names are composition metadata. They help a user choose Artifacts but do not themselves add another runtime authorization layer. ## Treat the response after the call Use `jsonRetain` to keep only required branches, then `jsonPatches` for explicit RFC 6902 transformations. Set `compact: true` only when nulls, empty strings, empty arrays, and empty objects carry no useful domain meaning. Enable TOON only after the JSON result has the intended information and shape. In the `1.0.0` runtime, the order is fixed: retain, patch, compact, then encode. Filters fail open in `1.0.0`: if a selected filter cannot parse or transform the response, the Gateway returns the original response. This avoids replacing a successful backend call with a filtering failure, but it means filtering must not be treated as a security boundary for removing sensitive fields. ## Three common decisions ### Too many Tools are visible 1. Start with an operation allowlist. 2. Exclude attached Custom Tool Artifacts that this consumer does not need. 3. Verify the result with `tools/list`. ### Low-level operations do not express the user task 1. Prefer one declarative Custom Tool over exposing several implementation-oriented operations. 2. Use a scripted Tool only if orchestration is required. 3. Keep only the generated target operations required by the Custom Tool. ### Tool results are too large or unstable 1. Retain only fields required by the task. 2. Patch the shape only when a stable transformation is needed. 3. Consider TOON after content reduction, not as a substitute for it. 4. Compare payloads using the same request and a published byte-counting method. ## Measure without overclaiming Measure the exact Tool list or response produced by two named configurations. Record the request, protocol version, filter, encoding, date, and byte-counting command. A reduction observed for one API response does not establish a universal token reduction or improved model accuracy. Continue with **[From API Contract to Agent Action](./api-to-agent.md)** for the complete request flow or **[Build a Scripted Custom Tool](../how-to-guides/build-scripted-custom-tool.md)** to implement a composed action. The **[Custom Tools](../references/custom-tools-specification.md)** and **[Tools Output Filtering](../references/spec-outtools-filtering.md)** pages remain the syntax references. --- ## Control Plane to Proxy Synchronization A reShapr proxy builds its local MCP surfaces from configuration owned by the control plane. It registers a logical Gateway, receives an initial snapshot, and then listens for Exposition changes over a gRPC stream. This is configuration propagation, not remote request proxying through the control plane: MCP clients call the proxy, and the proxy calls the configured backend. ## Synchronization sequence ```mermaid sequenceDiagram participant Proxy as reShapr proxy participant Control as Control plane participant Backend as Backend API Proxy->>Control: Register Gateway ID, labels, FQDNs, version, and token Control-->>Proxy: Matching Exposition snapshot Proxy->>Control: Subscribe to Exposition changes Control-->>Proxy: CREATED, UPDATED, or DELETED event Proxy->>Control: Advertise Gateway health periodically Note over Proxy: Build and retain local MCP registry Proxy->>Backend: Dispatch MCP Tool call ``` The proxy initiates the control-plane connections. Registration, discovery, health, and change streaming use the control-plane host, port, transport mode, and Gateway API token configured in the proxy runtime. ## 1. Register and fetch a snapshot At startup, the proxy sends: - its unique Gateway ID; - its labels; - the FQDNs advertised for MCP access; - its runtime version. The Gateway API token authenticates the gRPC calls. It is an infrastructure credential and is distinct from an API key or OAuth token used by an MCP client. The control plane registers the ephemeral Gateway representation and returns the Expositions selected for its labels. This response is the initial snapshot used to populate the proxy's local registry. A proxy that has not completed this discovery does not yet have a synchronized MCP surface. Gateway Group labels determine selection. One Gateway can match several groups, and one group can target several Gateways. Labels select configuration; they do not create network isolation or guarantee a service level. ## 2. Stream configuration changes After initial discovery, the proxy subscribes to the Exposition change stream. The released protocol defines three event types: | Event | Proxy action | |---|---| | `CREATED` | Fetch the selected Artifacts and add the Exposition's MCP surface to the local registry | | `UPDATED` | Fetch the selected Artifacts and replace the affected local Exposition | | `DELETED` | Remove that Exposition from the local registry | The control plane filters events according to Gateway Group matching. The proxy retries a failed stream subscription with backoff, while the initial registration and snapshot remain a separate startup step. Applying these events does not require a process restart. That property is accurately described as **live configuration propagation**. It does not prove that every request remains available during an update, process rollout, backend failure, or network partition. ## 3. Advertise health The proxy sends a health advertisement every two minutes after an initial delay. The control plane records the latest successful advertisement for the registered Gateway. The control plane runs stale-registration cleanup every five minutes and selects registrations whose last health advertisement is older than five minutes. Because cleanup is scheduled, five minutes is a threshold rather than an exact removal deadline. When a health response is not acknowledged, the proxy requests Gateway registration and initial discovery again. A transport exception is logged; it does not by itself clear the proxy's local registry. The change stream has its own retry behavior. ## 4. Operate through a connectivity loss An already initialized proxy keeps the local configuration it last fetched while synchronization is unavailable. Existing MCP surfaces can therefore remain usable when their local process, client route, credentials, and backend are healthy. During that period: - new or changed Expositions might not be visible locally; - deleted Expositions might remain until synchronization resumes; - token, backend, identity-provider, or local network failures can still prevent calls; - a restarted proxy still needs successful initial discovery before it can rebuild its registry. This behavior is not an offline-operation or availability guarantee. Monitor both Gateway health and configuration freshness according to the requirements of your environment. ## 5. Shut down On an orderly shutdown, the proxy cancels its change-stream subscription and sends a shutdown advertisement. The control plane can then remove its ephemeral Gateway registration. If the shutdown advertisement cannot be delivered, stale-registration cleanup provides eventual removal. Stopping a proxy does not delete its Gateway Group, Expositions, Services, or Configuration Plans. ## Protocol ownership The release-tagged [`eds-v1.proto`](https://github.com/reshaprio/reshapr/blob/1.0.0/api/src/main/proto/eds-v1.proto) owns the discovery snapshot and change-event contract. [`ghs-v1.proto`](https://github.com/reshaprio/reshapr/blob/1.0.0/api/src/main/proto/ghs-v1.proto) owns health and shutdown advertisements. The tracked [proxy runtime](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/ReshaprGatewayApp.java), [health advertiser](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/health/HealthAdvertiser.java), and [registration cleaner](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/control/GatewayRegistrationCleaner.java) define the `1.0.0` runtime behavior. ## Limits - Configuration streaming does not provide transactional changes across several Expositions. - Stream retry and health re-registration do not replace readiness checks, alerting, or operational recovery procedures. - Retaining the last local registry does not guarantee that its credentials, backend routes, or external dependencies remain valid. - Live propagation is not a zero-downtime deployment, upgrade, or rollback guarantee. - This page describes release `1.0.0`; timing and recovery behavior can change in later releases. ## Next step Use **[Deployment Models and Trust Boundaries](./deployment-models-trust-boundaries.md)** to place this synchronization channel in its wider network context. Then use **[Deploy a Hybrid reShapr Proxy](../how-to-guides/deploy-hybrid-gateway.md)** to register a Gateway, or **[Troubleshoot an Exposition or Proxy](../how-to-guides/operations/troubleshoot.md)** to diagnose registration and propagation failures. --- ## Deployment Models and Trust Boundaries reShapr separates configuration management from MCP request execution. The control plane stores and distributes desired configuration; proxies expose MCP endpoints and call backend APIs. You can place those components in one environment or split them across trust domains. Placement changes who operates each component and which networks data crosses. It does not, by itself, guarantee availability, isolation, or data residency. ## Planes and flows ```mermaid flowchart LR Admin[Administrator] -->|Configuration and identities| Control[Control plane] Proxy[reShapr proxy] -->|Gateway registration, health, discovery stream| Control Client[MCP client] -->|MCP requests and responses| Proxy Proxy -->|Backend requests and responses| Backend[Backend API] Proxy -.->|Optional authentication| IdP[Identity provider] Proxy -.->|Optional telemetry and audit| Observability[Observability systems] ``` These flows cross different trust boundaries: 1. **Administrator to control plane:** users, service accounts, and API tokens authorize management operations. 2. **Proxy to control plane:** a Gateway API token authenticates registration, discovery, and health traffic. This token is distinct from MCP endpoint credentials. 3. **MCP client to proxy:** an Exposition can use no authentication, an API key, or OAuth bearer JWT validation. 4. **Proxy to backend:** a Configuration Plan and backend Secret determine how the proxy authenticates to the API. 5. **Proxy to external systems:** identity, elicitation, audit, and telemetry integrations can introduce additional network and storage boundaries. Protect and review each boundary independently. Securing the MCP endpoint does not secure the backend connection, and encrypting control-plane storage does not configure TLS for public proxy traffic. ## The proxy is not an AI Gateway The reShapr proxy is the MCP data-plane component that serves Expositions and dispatches their Tool calls to configured APIs. It does not replace the broader model routing, provider abstraction, cost controls, or policy functions commonly associated with AI Gateway products. The two components can be complementary. When an AI Gateway supports the required MCP traffic and policies, it can be placed in front of an MCP endpoint served by a reShapr proxy. The AI Gateway then owns its routing and cross-cutting policies, while reShapr continues to own API-to-MCP transformation, Context Control, and backend dispatch. ## Compare deployment models | Model | Component placement | Main benefit | Operational responsibility | |---|---|---|---| | Local development | Control plane, proxy, database, and optional Web UI on one workstation or development network | Short feedback loop | The developer operates the complete temporary environment | | Centralized | Control plane and proxies in one managed environment, whether a data center or cloud account | One platform boundary and shared operations | The environment owner operates runtime, persistence, networking, and availability | | Hybrid or split | Control plane in one trust domain; one or more proxies close to clients or backends in another | Local backend connectivity and independently placed data planes | Control-plane and proxy owners share connectivity, credentials, rollout, and incident responsibilities | | Self-hosted or on-premises | Control plane, proxies, and persistence inside infrastructure operated by the organization | Direct ownership of the complete platform boundary | The organization operates every component and dependency | A topology can combine these models. For example, one control plane can synchronize proxies in several clusters or namespaces. Gateway Group labels select which Expositions each registered Gateway receives; labels do not create a network or security boundary on their own. Commercial availability, support, and service levels are separate from the runtime topology and are not inferred from these models. ## Understand data location The control plane holds the configuration required to build MCP surfaces, including Services, Artifacts, Configuration Plans, Expositions, Gateway Groups, and configured Secret material. Selected configuration is propagated to the proxies whose registered Gateways match those groups. MCP requests are handled by the proxy, which dispatches Tool calls to the configured backend. The control plane is not in this application-data path. However, this does not mean that all related data stays in the proxy's environment: - a backend endpoint can be outside the local trust domain; - OAuth and OIDC flows can contact an external identity provider; - audit events and telemetry can be exported to other systems; - credentials stored in the control plane cross the synchronization boundary when required by a proxy; - locally resolved `${env:VARIABLE}` references keep the resolved value in the proxy runtime, but the reference remains part of control-plane configuration. Map the actual endpoints and integrations for your deployment before making a residency claim. ## Plan network access The proxy initiates its Gateway registration, health, and discovery connections to the control plane. A hybrid proxy therefore needs an egress path to the control-plane gRPC endpoints; the control plane does not initiate a separate inbound connection to the proxy for synchronization. Other paths remain necessary: - MCP clients need access to the Gateway endpoints advertised by the proxy. - The proxy needs access to every backend selected by its Configuration Plans. - OAuth, elicitation, audit, and telemetry integrations need access to their configured endpoints. - Administrators and automation need access to the control-plane interfaces they use. Control-plane transport can be configured for plaintext or TLS. Use authenticated TLS across trust domains and treat plaintext as a bounded development choice. Public proxy TLS remains a deployment responsibility, such as an ingress, load balancer, or service mesh configured with certificates. ## Assign availability responsibilities Separating control and data planes can let an already synchronized proxy retain local configuration during a temporary control-plane connectivity failure. It does not guarantee uninterrupted traffic: process restarts, missing initial discovery, expired credentials, backend failures, network policy, and local capacity can still make an endpoint unavailable. Likewise, streamed configuration changes avoid restarting the proxy, but they do not guarantee zero-downtime upgrades or rollback. Define availability, recovery, token rotation, persistence, and observability for every environment that owns part of the topology. ## Limits - This page describes runtime placement and trust boundaries, not commercial deployment entitlements. - Gateway Group labels express selection criteria, not tenant isolation, network policy, or service levels. - The topology alone does not prove that data remains within a jurisdiction or trust domain. - Local Secret references currently provide the `env` resolver; they do not constitute a general external secret-provider integration. - Availability depends on the control plane, proxy, persistence, backend, identity, and network components selected by the operator. ## Next step Use **[Control Plane to Proxy Synchronization](./control-plane-gateway-synchronization.md)** to understand registration and configuration propagation. Then use **[Deploy a Hybrid reShapr Proxy](../how-to-guides/deploy-hybrid-gateway.md)** to run a published proxy image in another trust domain. For logical organization tenancy and administrative identities, see **[Multi-tenancy and Administrative Governance](./multi-tenancy-administrative-governance.md)**. For endpoint and backend authentication controls, see **[Security Capabilities and Limits](./security-model.md)**. --- ## Gateway Groups, Gateways, and Proxies Gateway Groups connect desired MCP configuration to logical Gateways registered by reShapr proxy instances. They let an organization target a changing set of serving instances without naming each proxy in every Exposition. The terms describe different layers: - a **Gateway** is the logical resource visible in the control plane, APIs, CLI, and Web UI; - a **Gateway Group** selects Gateways by labels and is the target referenced by an Exposition; - a **reShapr proxy** is the deployable data-plane process that registers a Gateway, exposes MCP endpoints, and calls backend APIs. The proxy is specific to reShapr's MCP data plane. It is not a general-purpose AI Gateway. Where broader model routing, policy, or traffic controls are required, a compatible AI Gateway can be deployed in front of the MCP endpoint served by the proxy. ## Gateway Groups are configuration targets A Gateway Group is a named, organization-owned resource with a set of labels. An Exposition targets one or more Gateway Groups instead of addressing individual proxy instances. Labels can express deployment criteria such as `environment=production`, `region=eu-west`, or `organization=acme`. Their meaning is an operator convention: a label does not by itself enforce network isolation, data residency, capacity, or a service level. ## Proxies register Gateways dynamically A proxy exposes MCP endpoints and dispatches Tool calls to backend APIs. When it starts, it registers a logical Gateway with its identity, labels, FQDNs, and version. The control plane uses those labels to find matching Gateway Groups and returns the Expositions the proxy must serve. This Gateway registration is ephemeral. Starting another proxy with matching labels makes its Gateway eligible for the same configuration; stopping a proxy does not delete the Gateway Groups or Expositions it matched. The synchronization unit delivered to the proxy is the Exposition. Its referenced **[Service](services-and-artifacts.md)** and **[Configuration Plan](configuration-and-exposition.md)** determine the resulting MCP surface. ## Matching is many-to-many A Gateway can match several Gateway Groups, and a Gateway Group can match several Gateways. For example, a Gateway registered by a proxy with the labels `organization=acme`, `environment=production`, and `region=eu-west` can match groups that select any compatible combination of those labels. This many-to-many relationship supports several runtime layouts without changing an Exposition whenever an individual proxy starts, stops, or is replaced. Duplicate or conflicting exposure behavior still depends on the routes, FQDNs, and Expositions configured by the operator. ## Related concepts - **[Deployment Models and Trust Boundaries](./deployment-models-trust-boundaries.md)** explains where control planes and proxies can run and which traffic crosses each boundary. - **[Control Plane to Proxy Synchronization](./control-plane-gateway-synchronization.md)** describes registration, initial discovery, change events, health, and recovery. - **[Deploy a Hybrid reShapr Proxy](../how-to-guides/deploy-hybrid-gateway.md)** applies this model to a proxy running in another trust domain. --- ## MCP Compatibility: Session and Stateless Modes reShapr supports several MCP protocol versions through one Streamable HTTP endpoint. The negotiated version determines how the client establishes context, which state it must carry, and which response shape the proxy returns. The important boundary is MCP `2026-07-28`. Earlier versions use a server-managed session. Version `2026-07-28` uses a stateless request model and a modern response dialect. :::tip Watch protocol negotiation The [MCP 2026-07-28 protocol demo](https://youtu.be/iqk3lcuASD8) shows the stateless mode in use. This explanation and the support matrix remain the canonical compatibility references. ::: ## Two modes share one endpoint The proxy selects a mode from the request and its headers: | Mode | Supported versions | Negotiation | State on later requests | |---|---|---|---| | Session-based | `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25` | The client calls `initialize` with a protocol version. | The client returns the server-issued `MCP-Session-Id`. | | Stateless | `2026-07-28` | The client calls `server/discover`. | Every request identifies its protocol version; no MCP session ID is created. | These modes are alternatives. A client must not add a legacy session ID to a stateless request or omit the session ID from a non-handshake legacy request. The five entries are versions reShapr `1.0.0` explicitly recognizes. This does not imply support for an unknown later MCP version: the modern request envelope is validated against the declared list before dispatch. ## Historical clients establish a session For a version before `2026-07-28`, `initialize` negotiates the protocol and creates an MCP session. The response includes `MCP-Session-Id`, and the proxy stores the negotiated version with that session. Later requests return the session ID. The proxy reads the pinned protocol version from its session store and selects the legacy response dialect. If a client sends a historical version on a non-handshake request without a valid session, the proxy rejects the request rather than silently creating one. The session can be shared across clustered proxy replicas through the configured runtime state store. This is protocol state, not an application login session and not a guarantee that a session survives every deployment or administrative operation. ## The public 2026-07-28 mode is stateless MCP `2026-07-28` replaces session initialization with `server/discover`. No `MCP-Session-Id` is issued. Each subsequent request carries the negotiated version in `MCP-Protocol-Version` and in `params._meta` under `io.modelcontextprotocol/protocolVersion`. Modern requests can also mirror body routing data in HTTP headers: - `Mcp-Method` mirrors the JSON-RPC `method`; - `Mcp-Name` mirrors `params.name` for `tools/call` and `prompts/get`, or `params.uri` for `resources/read`; - `MCP-Protocol-Version` mirrors the protocol version in `params._meta`. When a mirror header is present, it must agree with the body. A mismatch is rejected before method dispatch with HTTP `400` and JSON-RPC error `-32020`. An unsupported version in the modern envelope is rejected with HTTP `400` and error `-32022`. `2026-07-28` is a public version supported by reShapr `1.0.0`. It is not an experimental mode. ## Dialects change the result shape Both modes expose the same implemented Tools, Prompts, and Resources operations, but their result records differ. The legacy dialect returns the historical result shape. It deliberately omits modern-only fields such as `resultType`, `ttlMs`, and `cacheScope`. The modern dialect adds `resultType: complete` to completed results. When a Configuration Plan defines a client cache policy, modern list and read results can also include: - `ttlMs`, the suggested cache lifetime in milliseconds; - `cacheScope`, the suggested sharing scope. These values are hints for compatible clients. They do not create a proxy response cache, and historical dialects ignore them. ## Elicitation follows the state model Backend credential elicitation must preserve who supplied a secret without exposing it to the model context. The association changes with the protocol mode: | Mode | Elicitation response | Credential association | |---|---|---| | Session-based | Implementation-specific `URL_ELICITATION_REQUIRED` JSON-RPC error | The MCP session | | Stateless `2026-07-28` | An `input_required` result containing one or more `elicitation/create` requests | The authenticated user's issuer and subject | Stateless elicitation therefore requires an OAuth-protected Exposition that supplies a stable authenticated identity. This requirement concerns backend credentials requested during a Tool call; it is separate from choosing whether the MCP endpoint itself uses an API key or OAuth. See **[Authenticate Backend Calls and Use Elicitation](../how-to-guides/security/backend-auth-and-elicitation.md)** for the operational procedure and security boundaries. ## Constants are not capabilities The MCP schema contains names for methods used in requests, responses, and client/server interactions. A constant alone does not mean that reShapr implements that method as a server capability. The proxy dispatcher in `1.0.0` handles: - `initialize` and `server/discover` for the applicable lifecycle mode; - `tools/list` and `tools/call`; - `prompts/list` and `prompts/get`; - `resources/list`, `resources/templates/list`, and `resources/read`. Methods such as roots, sampling, completion, logging, and Resource subscriptions are not dispatched as reShapr server capabilities. In modern mode, methods removed by the `2026-07-28` revision, including `initialize`, `ping`, `logging/setLevel`, `resources/subscribe`, and `resources/unsubscribe`, return HTTP `404` with JSON-RPC `-32601`. Another unimplemented method returns the ordinary in-band `-32601` response. Use the **[MCP Support Matrix](../references/mcp-support.md)** for the method-level view. Use **[Test an MCP Endpoint](../how-to-guides/test-mcp-endpoint.md)** for executable requests in both modes. ## Choose a mode from the client Use the version and mode implemented by the MCP client that will call the endpoint. Prefer `2026-07-28` for clients that implement its stateless discovery and request envelope. Retain a historical version when the client still performs `initialize` and manages `MCP-Session-Id`. Do not translate one mode into the other by changing headers alone. Negotiation, state ownership, elicitation, and result shapes form one protocol contract. ## Evidence and limits This explanation describes reShapr `1.0.0`, verified on 2026-09-21. The release-tagged **[MCP schema](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/McpSchema.java)** owns the version list and protocol vocabulary. The **[MCP controller](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/McpController.java)** owns negotiation and dispatch, while the **[legacy](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/LegacyProtocolDialect.java)** and **[modern](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/ModernProtocolDialect.java)** dialects own response shaping. reShapr exposes MCP over Streamable HTTP. It does not provide a WebSocket MCP transport, and this page does not claim client-side support in any particular agent framework or SDK. --- ## Multi-tenancy and Administrative Governance reShapr uses organizations as its logical ownership and tenancy boundary. Services, Artifacts, Configuration Plans, Expositions, Secrets, Gateway Groups, Gateways, API tokens, and quotas belong to an organization. The current control plane separates their application queries by an organization discriminator in a shared PostgreSQL schema. This model provides logical application-level tenancy. It does not create a database, schema, Kubernetes namespace, cluster, network boundary, encryption key, or control-plane process per organization. ## Organization context selects the tenant An authenticated user can belong to several organizations, but each product API request runs in one current organization context. The resulting JWT carries that organization, and the control plane uses it to select tenant-aware rows. ```mermaid flowchart LR User[User] -->|membership| OrgA[Organization A] User -->|membership| OrgB[Organization B] User -->|select current organization| JWT[reShapr JWT] JWT -->|org claim| Resolver[Tenant resolver] Resolver -->|organization_id discriminator| Rows[(Shared PostgreSQL schema)] ``` A membership grants the user access to an organization through the normal identity flow. It does not provision infrastructure isolation. A user's default organization selects an initial context; switching organization changes the context for later API calls rather than merging data from several organizations into one request. An organization can have one owner. Ownership is administrative metadata and affects offboarding behavior; it is not a substitute for membership checks or deployment isolation. Use **[Manage Organizations, Owners, and Memberships](../how-to-guides/administration/organizations-and-memberships.md)** to create this boundary and grant users access through the Web UI, CLI, or administration API. ## Keep identity types separate reShapr uses different credentials at different boundaries: | Identity or credential | Boundary | Scope and purpose | |---|---|---| | User JWT | User or CLI to public control-plane APIs | Represents one user in one current organization context | | Control-plane admin API key | Administrator to admin APIs | Authorizes global administration across organizations | | Kubernetes service account exchange | Kubernetes workload to public control-plane APIs | Produces a short-lived reShapr JWT for one allowed organization | | Gateway API token | Proxy to control plane | Registers a Gateway and authorizes discovery and health traffic for its organization | | MCP endpoint API key | MCP client to proxy | Protects Expositions created from one Configuration Plan | | MCP OAuth bearer JWT | MCP client to proxy | Authenticates a caller and enforces the issuer, claim, and Exposition-scope policy | These credentials are not interchangeable. In particular, a Gateway API token does not authorize an MCP client, and a Configuration Plan API key does not grant administrative access to the control plane. ## Administrative access is global Administrative API routes require the control plane's `x-reshapr-api-key`. The CLI reads the same key from `RESHAPR_ADMIN_API_KEY` or an explicit option when running `reshapr admin` commands. This key can create and remove users and organizations, replace memberships, manage service accounts, and assign quotas. It is not scoped to one organization in release `1.0.0`. Treat it as a platform-wide privileged credential: - keep it in an approved secret manager; - expose it only to dedicated administration workloads; - do not pass it as a command argument when the environment-variable path is available; - restrict administrative network access independently; - audit its use through the surrounding platform controls. The admin API key is a deployment bootstrap and administration credential. It is distinct from the user JWT used by normal CLI commands. ## Workload identity with Kubernetes service accounts An administrator can register a reShapr service-account record with: - a unique reShapr name; - a Kubernetes subject in `namespace:service-account-name` form; - an expiry time; - an allow-list of organization names, or `*` for every organization. A Kubernetes workload presents its projected service-account JWT and names the target organization. In the `1.0.0` same-cluster flow, the control plane: 1. verifies the JWT signature with the local Kubernetes API server's JWKS; 2. requires the Kubernetes issuer, expiration, subject, and audience `https://app.reshapr.io`; 3. maps the JWT subject to the registered `namespace:service-account-name`; 4. rejects expired or unknown reShapr service-account records; 5. checks that the target organization is allowed; 6. returns a reShapr JWT valid for five minutes and scoped to that organization. ```mermaid sequenceDiagram participant Workload as Kubernetes workload participant Control as reShapr control plane participant K8s as Kubernetes API JWKS Workload->>Control: Projected JWT + target organization Control->>K8s: Fetch signing keys Control->>Control: Verify issuer, audience, subject, expiry Control->>Control: Match subject and organization allow-list Control-->>Workload: Five-minute reShapr JWT ``` The control plane fetches keys and trusts the CA of the cluster in which it runs. This released path does not establish a general cross-cluster workload-identity federation mechanism. The `*` organization allow-list is convenient for a shared operator but grants a broad boundary; prefer named organizations when one workload does not require global reach. Deleting a service-account record prevents future exchanges. Already issued reShapr JWTs remain bounded by their short expiry; deletion is not described as immediate revocation of every token already issued. ## Gateway API tokens are infrastructure credentials Gateway API tokens are created, listed, and deleted within an organization. A proxy uses the generated token to register its logical Gateway and authenticate configuration discovery and health advertisements to the control plane. Use a separate token for each operational boundary or proxy fleet so one rotation does not interrupt unrelated deployments. The generated token is shown once, has an explicit validity period, and must be delivered through a secret manager or workload Secret. Deleting a token prevents proxies using it from authenticating future control-plane connections. Rotate by creating a replacement, updating and verifying every affected proxy, then deleting the old token. **[Upgrade reShapr and Rotate Runtime Secrets](../how-to-guides/operations/upgrade-and-rotate.md#rotate-a-gateway-registration-token)** provides the applied procedure. ## Quotas govern resource counts Release `1.0.0` defines three organization quota metrics: | Metric | Counted resource | Consumption and release | |---|---|---| | `exposition.count` | Expositions | Consumed after successful creation and released after successful deletion | | `gateway-group.count` | Gateway Groups | Consumed after successful creation and released after successful deletion | | `gateway.count` | Ephemeral Gateway registrations | Consumed on first registration and released on shutdown or stale-registration cleanup | Each quota has an enabled flag, a limit, and a remaining count. A creation or first registration that has exhausted its enabled quota is rejected. A Gateway heartbeat refreshes an existing registration and does not consume another unit. These quotas limit governance resources. They do not measure MCP requests, backend calls, tokens, payload size, bandwidth, or execution time, and they do not provide throttling or rate limiting. Enforce request-level limits at an ingress, API gateway, service mesh, backend, or another component designed for that purpose. Quota enforcement does not allocate CPU, memory, network capacity, or availability. Capacity planning remains a deployment responsibility even when resource counts are bounded. Use **[Assign and Monitor Organization Quotas](../how-to-guides/administration/organization-quotas.md)** to apply these limits through the Web UI, CLI, or administration API and verify the resulting capacity from the organization context. ## Understand offboarding effects Administrative deletion has wider effects than removing a membership: | Operation | Effect in release `1.0.0` | |---|---| | Replace a user's memberships | Replaces the complete organization membership list for that user | | Delete a user | Removes memberships and the user; organizations they owned remain but become unowned | | Delete a service account | Removes the registered exchange identity; organizations and their resources remain | | Delete a Gateway API token | Invalidates that infrastructure credential; organization resources remain | | Delete an organization | Deletes its Services and dependent Artifacts, Plans, and Expositions; deletes its Secrets, Gateways, Gateway Groups, quotas, API tokens, and shared resources; detaches users | Exposition deletion during organization offboarding follows the normal propagation path so connected Gateways are told to stop routing those surfaces. Users detached from the deleted organization are not themselves deleted. The built-in `reshapr` root organization and its owner have deletion protections. Before deleting an organization, inventory its endpoints, workloads, credentials, owners, memberships, and retained data. The cascade is application cleanup, not a database backup or recovery mechanism. ## Choose stronger isolation when required Use separate reShapr deployments when policy requires stronger boundaries than organization discrimination, for example: - separate PostgreSQL instances or encryption domains; - separate administrative credentials and control-plane blast radii; - independent network policy, ingress, or egress enforcement; - separate clusters, regions, or legal data-residency boundaries; - independent upgrades, retention, recovery, or availability objectives. Organizations remain useful inside each deployment for delegated ownership and resource accounting. They should not be described as physical or cryptographic isolation. ## Limits - Organization tenancy in `1.0.0` is application-level discriminator tenancy in a shared schema. - The control-plane admin API key is platform-wide rather than organization-scoped. - The released Kubernetes service-account exchange is tied to the local cluster issuer, JWKS, CA, and expected audience. - Organization allow-lists constrain service-account token exchange; they do not create network isolation. - Resource quotas are counts, not MCP request-rate limits or infrastructure capacity guarantees. - Administrative deletion cascades do not provide backup, restore, legal retention, or immediate revocation of every previously issued short-lived token. ## Next step Use **[Deployment Models and Trust Boundaries](./deployment-models-trust-boundaries.md)** to choose physical placement and network boundaries. Use **[Security Capabilities and Limits](./security-model.md)** to distinguish control-plane, MCP endpoint, and backend credentials. Use **[Automate reShapr with the CLI in CI/CD](../how-to-guides/automate-with-cli-in-cicd.md)** to operate product resources from a controlled pipeline. The release-tagged [administrative API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-admin-ctrl-openapi-v0.1.yaml), [public API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml), and [administrative CLI reference](https://github.com/reshaprio/reshapr/blob/1.0.0/cli/ADMIN_CLI.md) remain the canonical interface references. --- ## Service, Artifact, Plan, Exposition, Gateway: The Lifecycle reShapr separates the description of an API, the MCP surface designed for a consumer, and the place where that surface runs. This separation lets one versioned Service support several agent-facing contracts without duplicating the backend API. ## The resource chain ```mermaid flowchart LR Main[Main API Artifact] --> Service[Service] Attached[Attached reShapr Artifacts] --> Service Service --> PlanA[Configuration Plan A] Service --> PlanB[Configuration Plan B] PlanA --> ExpoA[Exposition A] PlanB --> ExpoB[Exposition B] ExpoA --> Group[Gateway Group] ExpoB --> Group Group --> GatewayA[Gateway] Group --> GatewayB[Gateway] GatewayA --> Backend[Backend API] GatewayB --> Backend ``` | Resource | Owns or selects | Why it exists | |---|---|---| | **Artifact** | An API contract or an additional reShapr definition | Supplies the source material from which capabilities are derived | | **Service** | A name, version, API type, operations, and related Artifacts | Represents one versioned API promise | | **Configuration Plan** | Backend endpoint, operation and Artifact selection, credentials, and runtime policies | Defines one way to consume a Service | | **Exposition** | One Configuration Plan and one Gateway Group | Makes that Plan available to a target group | | **Gateway Group** | A logical set of Gateways | Selects where Expositions are distributed | | **Gateway** | The synchronized Expositions it serves | Exposes MCP endpoints and dispatches calls to backends | ## Artifacts define and enrich a Service The first imported OpenAPI, GraphQL, or Protocol Buffer definition becomes the Service's **main Artifact**. It determines the Service identity, type, and backend operations. Additional reShapr Artifacts can contribute Prompts, Resources, Custom Tools, or Tool output filters. The main Artifact is always available to Plans for that Service. Attached reShapr Artifacts are selectable by name through `includedArtifacts`. An empty selection means that all attached Artifacts apply. Re-importing the main Artifact with the same Service name and version updates that Service and its operations. Attaching a custom Artifact again with the same source replaces its derived content and recalculates the capabilities declared by that Artifact. ## Plans create distinct MCP surfaces A Service can have several Configuration Plans. Each Plan can choose a different: - operation allowlist or denylist; - set of attached Artifacts; - backend endpoint and credentials; - endpoint authentication, cache, audit, and output behavior. This is the boundary at which an API surface becomes a consumer-specific MCP surface. A narrow Plan does not change the Service or another Plan derived from it. ## Expositions place Plans on Gateways A Configuration Plan is not an endpoint by itself. An Exposition assigns it to a Gateway Group. Connected Gateways in that group receive the Exposition and the selected Artifacts through the control-plane discovery stream. Updates to a Service, Plan, or selected Artifact are propagated to affected Gateways. This is live configuration propagation, not a guarantee that every in-flight call or infrastructure upgrade is interruption-free. ## Deletion follows dependencies Deletion has consequences downstream: - Deleting an Exposition removes that endpoint assignment from its Gateway Group. - Deleting a Configuration Plan removes its Expositions. - Deleting a Service removes its Artifacts, Plans, and Expositions. - Deleting an attached Artifact removes its name from Plans that selected it and propagates the change. There is one subtle case: if deleting an Artifact leaves a Plan with an empty `includedArtifacts` list, that empty list means **all remaining attached Artifacts apply**. Review the deletion impact before confirming it; a removal can therefore broaden the set selected by that Plan. ## Choose the boundary you intend to change Change the **Service** when the source API contract changed. Change or attach an **Artifact** when adding agent-oriented capabilities or response treatment. Change a **Plan** when one consumer needs a different surface or policy. Change an **Exposition** or **Gateway Group** when the same Plan must run elsewhere. Continue with **[From API Contract to Agent Action](./api-to-agent.md)** to follow one request through these resources, or **[Configuration Plan and Exposition](./configuration-and-exposition.md)** for the policy boundary in more detail. The evolving implementation is owned by the [reShapr runtime repository](https://github.com/reshaprio/reshapr). The [public API contract for release 1.0.0](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml) is the versioned interface source for executable examples. --- ## Security Capabilities and Limits reShapr separates three trust boundaries that require independent controls: 1. **MCP client to proxy:** the proxy decides whether a client can access an Exposition. 2. **Proxy to backend API:** after accepting the MCP request, the proxy authenticates to the REST, GraphQL, or gRPC backend with the credentials configured for that Service. 3. **Proxy to control plane:** the proxy uses a dedicated Gateway API token to register its logical Gateway, advertise health, and synchronize configuration. Protecting one boundary does not protect the others. For example, an API key can restrict access to the MCP endpoint while a separate Secret authorizes the resulting backend call. The Gateway API token authenticates synchronization, not MCP clients or backend requests. ## Choose controls by boundary | Boundary | Available control | What it establishes | Important limit | |---|---|---|---| | MCP client to proxy | None | No authentication is performed by reShapr | Appropriate only when another trusted layer controls access or for a bounded test | | MCP client to proxy | API key | Possession of the Configuration Plan key | No user identity or scopes; the policy covers the complete Exposition | | MCP client to proxy | OAuth 2.0 bearer JWT | Signed token, accepted issuer, audience, required claims, and configured scopes | Scopes and audiences cover the complete Exposition, not individual Tools, Prompts, or Resources | | Proxy to HTTP backend | Token or username/password Secret | Bearer or custom-header token, or HTTP Basic authentication | Independent from MCP endpoint authentication | | Proxy to HTTP, GraphQL, or gRPC backend | OAuth 2.0 Client Credentials Secret | A cached machine-to-machine access token obtained from the backend Authorization Server | No user elicitation or refresh-token flow | | Proxy to gRPC backend | Token Secret | Authorization metadata or a configured metadata key | Username/password is not applied as gRPC Basic authentication | | Proxy to gRPC backend | CA certificate Secret | Trust material for the backend TLS channel | This is a custom trust anchor, not a client certificate identity | | Proxy to backend | Elicited credential | A credential associated with the requesting MCP session or authenticated user | Requires a compatible client flow and does not replace MCP endpoint authentication | | Proxy to control plane | Gateway API token | Gateway registration, discovery, and health authorization | Separate from an MCP API key and backend Secret | ## MCP endpoint controls A Configuration Plan selects one endpoint access mode: - **None:** the proxy does not authenticate the MCP client. Use this only when access is controlled elsewhere or for a bounded test environment. - **API key:** the proxy compares the `x-reshapr-key` request header with the key assigned to the Configuration Plan. A renewed key is propagated to connected proxies. - **OAuth 2.0 bearer JWT:** the proxy verifies RSA and RSA-PSS signatures with a configured JWKS, accepts configured issuers, checks the required `sub`, `iat`, `exp`, and `aud` claims, and, when scopes are configured, requires them for the Exposition. By default, at least one audience must match the requested Exposition URL or a configured static audience. This policy applies to the entire Exposition, not to individual Tools, Prompts, or Resources. The proxy publishes OAuth 2.0 Protected Resource Metadata as defined by [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728). A missing or invalid bearer token, including a missing required `aud` claim, produces a `401` response. An audience mismatch or missing required scope produces `403`. Its OAuth configuration refers to Authorization Server URLs and a JWKS URI. reShapr does not host an Authorization Server Metadata endpoint defined by [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). The expected dynamic audience is the requested Exposition URL; configured static audiences are accepted as alternatives. Audience validation can be disabled for compatibility, but doing so weakens confused-deputy protection. reShapr validates the resulting token; it does not perform the token-request flow described by [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html). TLS for the client-to-proxy connection is a deployment responsibility. For example, a Kubernetes Ingress can terminate TLS when configured with a certificate; TLS is not enabled merely by choosing API key or OAuth authentication. ## Backend authentication {#gateway-access-to-backend-apis} A backend Secret is independent from MCP endpoint authentication. For REST and GraphQL calls, a token becomes an `Authorization: Bearer` header unless `tokenHeader` names another header. A username and password become HTTP Basic credentials. For gRPC, a token becomes per-call metadata; a PEM certificate configures a custom trust manager for a TLS backend. Release `1.0.0` also supports OAuth 2.0 Client Credentials for REST, GraphQL, and gRPC backends. The proxy resolves the client ID and secret, requests an access token from the configured token endpoint, and caches it until shortly before expiration. This is a machine-to-machine flow: it does not elicit user credentials and does not use refresh tokens. The Secret fields are not a promise that every combination applies to every backend protocol. HTTP Basic credentials are handled by the HTTP proxy, while custom CA trust material is handled by the gRPC proxy. ### Request header propagation A Configuration Plan can allow, deny, or rename request headers before an HTTP or gRPC backend call. For gRPC, surviving headers become call metadata, except `Accept`, `Content-Type`, and `User-Agent`, which the transport manages. The proxy always removes hop-by-hop headers, reShapr authentication headers, and MCP transport headers. Without an explicit policy, it also removes `Authorization` and `Cookie`; explicitly allowing or renaming a header can opt in to the backend credential contract you intend. Only the client-to-backend request direction is enforced in `1.0.0`. Response rules are represented in the API and Kubernetes CRD but are reserved for future use. Header propagation shapes transport metadata; it does not replace backend authorization. ### Secret references Backend Secrets can contain literal values stored by the control plane or references resolved locally by a proxy. The `${env:VARIABLE}` scheme lets a hybrid proxy retrieve a sensitive value from its own environment when preparing a backend call, so the control plane stores and propagates only the reference. A value can contain several placeholders, and literal text can surround them. The current implementation provides only the `env` resolver. An unknown scheme or missing environment variable fails resolution rather than falling back to a literal credential. The release-tagged [public API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml) defines the Secret fields, while the [secret resolver](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/secret/SecretReferenceResolver.java) defines `1.0.0` resolution behavior. ## Elicited credentials An Exposition can request a user-specific backend credential instead of relying only on a pre-provisioned Secret. The [MCP elicitation specification](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) distinguishes form mode, in which data passes through the MCP client, from [URL mode](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-requests), in which the user completes an out-of-band interaction in a browser. Sensitive credentials must not use MCP form mode because that would expose them to the MCP client. reShapr uses URL mode for both backend authentication flows it supports. The client sees the URL and explanatory message, but the credential entered or issued through that URL does not pass through the MCP client or the LLM context. ### Collect a backend credential For a backend API key or token, the proxy returns a URL for its own elicitation page. After the user consents to opening it, the browser sends the credential directly to the proxy. The proxy stores the value for the initiating session or authenticated user and applies it to subsequent backend calls. This follows the specification's [URL mode pattern for sensitive data](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-sensitive-data). The MCP client receives neither the submitted credential nor a form-mode response containing it. It only coordinates opening the URL and retrying or resuming the Tool call. ### Authorize access through OAuth 2.0 When the Secret contains an OAuth client configuration, the elicitation URL starts an OAuth 2.0 authorization-code flow instead of displaying a credential form. The browser follows the proxy redirect to the backend's Authorization Server, where the user authenticates and grants access. The Authorization Server returns the code to the proxy callback, and the proxy exchanges it for an access token and stores that token for backend calls. The authorization UI, authorization code, and resulting access token do not pass through the MCP client or LLM context. This is the [URL mode pattern for third-party OAuth authorization](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-oauth-flows), not the OAuth flow that authenticates the MCP client to the Gateway. The two tokens protect different boundaries and are not interchangeable. ### Bind the result to a caller The storage boundary depends on the negotiated MCP version. Protocol versions before `2026-07-28` bind the elicited value to a replicated MCP session and return a `URL_ELICITATION_REQUIRED` error when input is needed. The public `2026-07-28` protocol follows the current [multi-round-trip elicitation model](https://modelcontextprotocol.io/specification/draft/client/elicitation): it returns an `input_required` result containing `elicitation/create` requests and binds the resulting value to the authenticated user's JWT issuer and subject. Stateless elicitation therefore requires a stable authenticated identity. **[MCP Compatibility](./mcp-compatibility.md)** compares both state models and their response dialects. In `1.0.0`, the proxy associates the completed interaction with the initiating session or MCP identity and validates the opaque OAuth `state` value in stateless callbacks. Its elicitation web routes do not independently reauthenticate the browser user as that same identity. Treat the elicitation URL and identifier as sensitive, show the complete target domain before opening it, never share the URL, and use HTTPS outside local development. ## Storage, propagation, and audit The control plane encrypts selected sensitive Configuration Plan and Secret fields with AES-256-GCM keys identified by a `kid`. Each value carries its key identifier and a random IV, providing authenticated encryption. Release `1.0.0` can still decrypt legacy AES/ECB values during migration and provides administrator-only status and re-encryption commands. Key generation, distribution, activation, invocation of the rotation command, retirement, and backup recovery remain operator responsibilities. All control-plane replicas must receive the complete key set before the active key changes, and old keys must remain available until status and rotation checks show that no stored value depends on them. Configuration updates, including API key renewal, are propagated to connected proxies over the control-plane discovery stream. Applying a configuration change does not require a proxy restart, but this is not an immediate-propagation guarantee. When audit is enabled on a Configuration Plan, the proxy emits structured events for MCP calls and authentication failures. OpenTelemetry export for proxy traces, metrics, and logs must also be configured. This is fully pluggable with the OpenTelemetry Collector or solution of your choice. You must build dashboards or equivalent telemetry coverage for the control plane, Web UI, operator, and admission controller using your chosen solution. ## Canonical sources - The [`1.0.0` public API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-public-openapi-v0.1.yaml) owns Configuration Plan and Secret fields. - The [`1.0.0` endpoint security implementation](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/security/SecureEndpointFilter.java) defines API-key and OAuth token validation. - The [`1.0.0` HTTP](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/proxy/ProxyService.java) and [gRPC](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/proxy/GrpcProxyService.java) proxy implementations define backend credential handling. - The [`1.0.0` encryption implementation](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/security/CipherService.java) defines the control-plane encryption behavior. ## Next step - **[Protect an MCP Endpoint with an API Key](../how-to-guides/security/api-key.md)** for a reproducible endpoint-access procedure. - **[Protect an MCP Endpoint with OAuth 2.0](../how-to-guides/security/oauth.md)** to configure issuers, JWKS, scopes, and rejection checks. - **[Authenticate Backend Calls and Use Elicitation](../how-to-guides/security/backend-auth-and-elicitation.md)** to apply stored, local, or user-provided backend credentials. - **[Audit MCP Endpoint Calls](../how-to-guides/audit-mcp-endpoint.md)** to enable audit on a Configuration Plan and inspect its event attributes. - **[Observe the reShapr Proxy](../how-to-guides/operations/observe-and-audit.md)** to export telemetry and route audit logs to a dedicated sink. - **[Upgrade reShapr and Rotate Runtime Secrets](../how-to-guides/operations/upgrade-and-rotate.md)** to renew API keys, Gateway tokens, and local backend credentials. - **[Multi-tenancy and Administrative Governance](./multi-tenancy-administrative-governance.md)** to distinguish users, service accounts, administrative credentials, Gateway tokens, and organization boundaries. - **[Deployment Models and Trust Boundaries](./deployment-models-trust-boundaries.md)** to place these controls in their network context. - **[Control Plane to Proxy Synchronization](./control-plane-gateway-synchronization.md)** for configuration propagation and recovery behavior. --- ## Services and Artifacts A reShapr **Service** is the versioned model of an API that can be shaped and exposed through MCP. **Artifacts** provide the contracts and complementary definitions from which reShapr builds that model. ## The main Artifact defines the Service Importing an API contract through the CLI, Web UI, or API creates or updates a Service. The imported contract becomes its **main Artifact** and determines: - the Service name and version; - the source protocol: OpenAPI, GraphQL, or gRPC; - the operations from which reShapr can generate MCP Tools. Only one main Artifact belongs to a Service. It is always available to the Service's Configuration Plans and cannot be excluded through `includedArtifacts`. reShapr derives Service identity differently for each contract type: | Contract | Service identity | |---|---| | OpenAPI 3.x | The document's title and version identify the Service. | | gRPC/Protobuf | The first `service` definition and its package identify the Service; the final package segment supplies its version. | | GraphQL | The schema has no Service metadata, so the importer must provide the name and version. | Importing another contract with the same name and version updates the existing Service. A different version creates a separate Service that can have its own Plans and Expositions. ## Attached Artifacts enrich the Service Attached reShapr Artifacts add capabilities or transformations without changing the source API contract: | Artifact type | Contribution | |---|---| | **[Prompts](../references/prompts-specification.md)** | Reusable instructions exposed through MCP Prompts. | | **[Resources](../references/resources-specification.md)** | Static or remote context exposed through MCP Resources. | | **[Custom Tools](../references/custom-tools-specification.md)** | Task-oriented Tools that map to or orchestrate API operations. | | **[Tools Output Filters](../references/spec-outtools-filtering.md)** | Response retention, JSON Patch transformations, and optional TOON encoding. | Each attached Artifact declares the target `service.name` and `service.version`. Attaching the same source again updates the Artifact and recomputes its metadata. ## Derived capabilities make composition visible When reShapr imports an attached Artifact, it extracts a concise capability list: - Prompt names from `prompts`; - Custom Tool names from `customTools`; - Resource and Resource Template URIs from `resources` and `resourceTemplates`; - target Tool names from `filters`. These derived capabilities help operators inspect what an Artifact contributes without reading its complete source. They preserve declaration order and remove duplicates. They are available through Artifact API representations, the CLI, and the Web UI's Service, Artifact, and Plan views. Capability metadata is descriptive. It does not replace MCP discovery, authorize a Tool call, or guarantee that a capability is included in every Exposition. ## Configuration Plans select attached Artifacts A Service can have several **Configuration Plans**, each selecting a different set of operations and attached Artifacts. `includedArtifacts` contains Artifact names: - a non-empty list selects only those attached Artifacts; - an absent or empty list selects all attached Artifacts; - the main Artifact, and any schema derived from it, remain available regardless of this setting. This lets one Service support distinct MCP surfaces. For example, an internal Plan can include operational Resources and detailed output, while a partner Plan selects a narrower Custom Tool and response filter. The selected Artifacts contribute to every Exposition created from that Plan. See **[Service, Artifact, Plan, Exposition, Gateway: The Lifecycle](./resource-lifecycle.md)** for the complete resource chain. ## Updates and deletion propagate Updating a main or attached Artifact refreshes the affected Service representation. Connected Gateways receive the resulting Exposition updates through control-plane discovery. Deletion has wider consequences: - deleting an Exposition removes its endpoint from the target Gateway Group; - deleting a Configuration Plan removes its Expositions; - deleting a Service removes its Artifacts, Plans, and Expositions; - deleting an attached Artifact removes its name from Plans that selected it. The last case needs particular care. If removing an Artifact leaves a Plan with an empty `includedArtifacts` list, that empty list means **all remaining attached Artifacts**. Adjust or remove the Plan before deleting the Artifact when this fallback would broaden its MCP surface. ## Apply the model - **[Import OpenAPI, GraphQL, or Protobuf Artifacts](../how-to-guides/import-api-artifacts.md)** covers format recognition, Service identity, source selection, and dependency failures. - **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)** shows how to inspect capabilities and create two Plan-specific selections. - **[Context Control](./context-control.md)** compares operation selection, Artifact selection, Custom Tools, and output transformations. - **[CLI Commands](../references/cli-commands.md)** documents Artifact inspection commands and options. --- ## reShapr CLI Reference ## Authentication commands ### `reshapr login` command The login command authenticates you against a reShapr control plane. Depending on the server configuration (SaaS, on-premises with OIDC, or on-premises with local credentials), the CLI will automatically select the appropriate authentication flow. ```bash reshapr login [options] ``` Available options: - `-u, --username ` : Your reShapr username (for password-based login) - `-p, --password ` : Your reShapr password (for password-based login) - `-o, --org ` : Your reShapr organization name - `-s, --server ` : Your reShapr Control Plane URL (defaults to `https://try.reshapr.io`) - `-k, --insecure` : Skip SSL certificate validation - `--password-stdin` : Read password from stdin (mutually exclusive with `-p`) :::info For SaaS or OIDC-enabled on-premises servers, the CLI will automatically open your browser for the authorization flow. A temporary local server is started to receive the authentication callback. ::: Here's an example of logging in to a local on-premises server: ```bash reshapr login --server http://localhost:5555 ``` For non-interactive environments (CI/CD), you can pipe the password from stdin: ```bash echo "$RESHAPR_PASSWORD" | reshapr login -u admin --password-stdin --server http://localhost:5555 ``` Use **[Automate reShapr with the CLI in CI/CD](../how-to-guides/automate-with-cli-in-cicd.md)** for a complete structured-output workflow with explicit postconditions and controlled deletion. ### `reshapr logout` command The logout command clears your local authentication session. ```bash reshapr logout ``` ### `reshapr info` command The info command displays information about your current authentication context and the connected reShapr server. ```bash reshapr info ``` Example output (identity, deployment mode, version, and timestamps vary): ```text ℹ️ User Information User : admin Organization: my-org Server : https://try.reshapr.io ℹ️ Server Information Version : 0.2.3 Build time : Mode : saas Internal IDP: https://idp.reshapr.io ``` ### `reshapr switch-org` command Switches the current CLI context to another organization of which the authenticated user is a member. The CLI stores the replacement token for subsequent commands. ```bash reshapr switch-org ``` ## Artifact commands ### `reshapr import` command The import command allows you to push (or have reShapr pull) a new artifact into reShapr. This will allow it to discover a new Service as explained in **[Services & Artifacts](../explanations/services-and-artifacts.md)**. For a task-oriented comparison of supported formats, local files, remote URLs, and dependency resolution, see **[Import OpenAPI, GraphQL, or Protobuf Artifacts](../how-to-guides/import-api-artifacts.md)**. First, you have to tell reShapr how to proceed with retrieving this artifact. You can use: - `-f, --file ` to reference a local file you want to upload, - `-u, --url ` to ask the reShapr control plane to download a remote file. :::info When specifying a remote URL with the `-u` option, it can also be useful to specify a Secret with the `-s, --secret ` option to authorize access to the remote endpoint. Check the **[Secret commands](cli-commands.md)** just below to learn how to create them. ::: The discovery of the artifact Service is automatic for **[OpenAPI 3.x](https://www.openapis.org/)** specs and **[gRPC/Protobuf](https://grpc.io/)** definitions. By default, reShapr will use the identification elements (`name` and `version`) found in the artifact. You can, however, decide to override this information by using the additional `--sn, --serviceName ` and `--sv, --serviceVersion ` options. :::warning The discovery of services from **[GraphQL](https://graphql.org/)** schemas needs some more help. Here, the additional `--sn, --serviceName ` and `--sv, --serviceVersion ` options are mandatory. If not specified, the import will fail. ::: When importing an artifact Service, you may also choose not to consider all the different operations that will be discovered. Perhaps you want to restrict it to read-only access, or maybe your existing API is too coarse-grained, and you want to filter on a single domain. Whatever the reason, you can configure this with the following options: - `--io, --includedOperations [, ]` : Allow specifying a list of operations to consider. Example: `--io '["createLabel", "createIssue"]'` , - `--eo, --excludedOperations [, ]` : Allow specifying a list of operations to ignore. Example: `--eo '["POST /order"]'` . This exclusion list will only be considered if no inclusion list is specified. The import command also allows you to quickly configure and expose the discovered Service using additional flags! If you add the `--be, --backendEndpoint ` flag to your command, this will create a *default* **[Configuration Plan & Exposition](../explanations/configuration-and-exposition.md)** for you, using the *default* gateways. This exposition can be further configured with the most common options: - `--apiKey` : Allow the generation of an API key to secure access to an MCP endpoint exposed by gateways. See the **[`reshapr config create` command details](cli-commands.md)** below - `--internalOAuth2` : Allows secure access to an MCP endpoint using OAuth 2 authorization backed by the reShapr Internal OAuth Identity Provider. See the **[`reshapr config create` command details](cli-commands.md)** below - `--bs, --backendSecret ` : Allow the specification of a Backend Secret to use when exposing an MCP Endpoint on gateways. See **[Backend Secrets](../explanations/security-model.md)**. Below is an example of an all-in-one command that imports a local GraphQL schema, sets its name and version, and exposes it with an API key so that it targets the GitHub GraphQL endpoint: ```bash reshapr import -f ../dev/github-api.graphql --sn 'GitHub GraphQL' --sv '20250917' --be https://api.github.com/graphql --apiKey ``` ```bash ✅ Import successful! ℹ️ Discovered Service GitHub GraphQL with ID: 0N2G4YZFDD3ZF ⚠️ The API Key to access future expositions is: c8d90391-xxxx-xxxx-xxxx-xxxxb004dfdb ⚠️ Make sure to store it securely, as it will not be shown again. ✅ Exposition done! ✅ Exposition is now active! Exposition ID : 0N2G4Z0ASD034 Organization : Created on : 2025-09-26T14:43:37.686+00:00 Service ID : 0N2G4YZFDD3ZF Service Name : GitHub GraphQL Service Version: 20250917 Service Type : GRAPHQL -> https://api.github.com/graphql Endpoints : mcp.beta.reshapr.io/mcp//GitHub+GraphQL/20250917 ``` ### `reshapr attach` command The attach command allows you to provide and attach complementary artifacts to an already discovered **[Service](../explanations/services-and-artifacts.md)**. This command will typically be used immediately after the `import` command to provide additional information about **[Prompts](prompts-specification.md)** or **[Custom Tools](custom-tools-specification.md)**. Similar to the `import` command, you need to instruct reShapr on how to retrieve this artifact. You can use: - `-f, --file ` to reference a local file you want to upload, - `-u, --url ` to ask the reShapr control plane to download a remote file. :::info When specifying a remote URL with the `-u` option, it can also be useful to specify a Secret with the `-s, --secret ` to authorize access to the remote endpoint. Check the **[Secret commands](cli-commands.md)** just below to learn how to create them. ::: Here's an example of an artifact attachment: ```bash reshapr attach -f ../dev/github-api-prompts.yaml ``` ```bash ✅ Attachment successful! ℹ️ Discovered Artifact file with ID: 0NKVYHWSR9VPT ``` ### `reshapr artifact list` command The artifact list command lets you list all artifacts associated with a given Service. ```bash reshapr artifact list -s ``` Available options: - `-s, --serviceId ` (required) : Filter artifacts by service ID - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr artifact list -s 0N2G4YZFDD3ZF ``` ```bash ID NAME TYPE MAIN 0NKVYHWSR9VPT github-api.graphql GRAPHQL_SCHEMA Yes 0NKVZAB12X3YZ github-api-prompts RESHAPR_PROMPTS No ``` ### `reshapr artifact get` command This command retrieves details of a specific artifact by its ID. ```bash reshapr artifact get [options] ``` Available options: - `-d, --display` : Display the artifact content (syntax-highlighted) - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr artifact get 0NKVYHWSR9VPT ``` ```bash ℹ️ Artifact details ID : 0NKVYHWSR9VPT Name : github-api.graphql Organization : my-org Service ID : 0N2G4YZFDD3ZF Type : GRAPHQL_SCHEMA Main Artifact: Yes Source : github-api.graphql Path : N/A ``` ### `reshapr artifact delete` command Deletes an artifact by ID and reports the effect on Configuration Plans that reference it before asking for confirmation. ```bash reshapr artifact delete [options] ``` Available options: - `-f, --force` : Skip the confirmation prompt ## Service commands ### `reshapr service list` command Lists all services registered in your organization. ```bash reshapr service list [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr service list ``` ```bash ID NAME VERSION TYPE AGE 0N0802V07SZEH Open-Meteo APIs 1.0 REST 3d 0N2G4YZFDD3ZF GitHub GraphQL 20250917 GRAPHQL 1d ``` ### `reshapr service get` command Retrieves detailed information about a specific service, including all discovered operations. ```bash reshapr service get [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr service get 0N0802V07SZEH ``` ```bash ℹ️ Service details ID : 0N0802V07SZEH Name : Open-Meteo APIs Version : 1.0 Organization: my-org Type : REST Created : 2025-09-19T14:35:58.591+00:00 Operations : - Name: GET /v1/forecast - Name: GET /v1/historical ``` ### `reshapr service delete` command Deletes a service by its ID. This will also remove all associated artifacts, configuration plans, and expositions. ```bash reshapr service delete [options] ``` Available options: - `-f, --force` : Skip confirmation prompt :::warning Deleting a service is a destructive operation. All associated artifacts, configuration plans, and expositions will also be removed. ::: ```bash reshapr service delete 0N0802V07SZEH ``` ```bash ? Deleting this service will also remove associated artifacts, config plans & expositions. Are you sure you want to proceed? Yes ✅ Service 0N0802V07SZEH deleted successfully. ``` ## Secret commands ### `reshapr secret create` command Creating a **[Secret](../explanations/security-model.md)** just requires an argument that will be its ``. You will also be able to provide a description using the `--description ` option. Moreover, the command proposes several options for providing the different elements of your **[Secrets](../explanations/security-model.md)**. First, you can tag your Secret so that you'll later know the target usage: - `-A, --artifact` : Tags the Secret as being used for artifact retrieval on secured remote repositories, - `-B, --backend` : Tags the Secret as being used for accessing a secure Backend Endpoint. Then, depending on your Secret's nature, you will have to use one or more of the following options: - `-u, --username ` : When your remote endpoint is secured using the HTTP Basic mechanism, you have to provide a username, - `-p, --password ` : The password is the companion of the username when using HTTP Basic for authenticating to the resource. - `-t, --token ` : When using token or API key-based authentication, you'd usually provide a token. If no additional `tokenHeader` is provided, it will be used as the value in an `Authorization: Bearer ` HTTP header. - `-h, --tokenHeader ` : When using token or API key-based authentication, the token header will allow you to customize the HTTP header used to send the token value. - `-c, --certificate ` : When accessing a TLS-secured endpoint, you may want to provide your specific X509 certificate using the PEM format. Since reShapr `0.0.14`, sensitive backend Secret values can be literal values or **[secret references](../explanations/security-model.md#secret-references)** such as `${env:GITHUB_TOKEN}`. In that case, the control plane stores the reference, and the Gateway resolves the actual value locally when calling the backend endpoint. Below is an example of a secret creation: ```bash reshapr secret create my-secret --description 'A secret to access a super secured endpoint' -B \ -t acme_wXl53oz5gFeSmBt8awTUJI72yQSrtbMP -h x-acme-api-key --certificate ../certs/my-secret.pem ``` ```bash ✅ Secret my-secret created successfully with ID: 0N084D4H90937 ``` ### `reshapr secret create-elicitation` command This command is actually an alias of the `secret create` command, which allows the creation of an **[Elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation)**-based backend secret. Unlike a standard backend Secret, an Elicitation-based one does not require proactive provisioning: when needed, the reShapr MCP Server will return to the user to request backend credentials or initiate an OAuth authorization flow. Like the `secret create` command, you have to assign your secret a `name` and an optional `description`. Two URL Elicitation flows are available: the [**URL Mode Elicitation for Sensitive Data**](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-sensitive-data) and the [**URL Mode Elicitation for OAuth Flows**](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-oauth-flows). Depending on the one you want to configure, you'll have to use different options flags. - For [**URL Mode Elicitation for Sensitive Data**](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-sensitive-data), use the `-t, --token ` option flag to provide the name of the token collected from the user and set as a header to the backend remote endpoint, - For [**URL Mode Elicitation for OAuth Flows**](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-for-oauth-flows), you have to provide **three mandatory options** (plus one optional): - `--oc, --oauth2ClientID ` : Allows configuring the client ID used for the third-party authorization server, - `--ocs, --oauth2ClientSecret ` : Allow to specify the client secret for the backend authorization service (if required by the authorization server), - `--oae, --oauth2AuthorizationEndpoint ` : Allow the specification of the Authorization endpoint for the backend authentication (including query parameters but without `clientID` and `redirect_uri` that will be added dynamically), - `--ote, --oauth2TokenEndpoint ` : Allow the configuration of the token exchange endpoint for the backend authentication. Below is an example of how to create such an Elicitation-based Secret: ```bash reshapr secret create-elicitation 3rd-party-oauth --oc reshapr-saas \ --oae https://idp.example.com/realms/3rdparty/protocol/openid-connect/auth\?scope\=openid\%20profile\&response_type\=code\&prompt\=login \ --ote https://idp.example.com/realms/3rdparty/protocol/openid-connect/token ``` ```bash ✅ Elicitation secret 3rd-party-oauth created successfully with ID: 0NWN2WHGEA2JP ``` You will then be able to use and reference this Secret when creating your Configuration Plan to enable Elicitation-based security. ### `reshapr secret create-client-credentials` command Creates a machine-to-machine backend Secret using the OAuth 2.0 Client Credentials grant. `--oauth2ClientID` and `--oauth2TokenEndpoint` are required; the client secret may be an `${env:VARIABLE}` reference resolved by the proxy. ```bash reshapr secret create-client-credentials backend-machine-identity \ --oauth2ClientID '' \ --oauth2ClientSecret '${env:BACKEND_OAUTH_CLIENT_SECRET}' \ --oauth2TokenEndpoint 'https://idp.example.com/token' \ --oauth2Scopes 'backend.read,backend.write' ``` The proxy requests and caches an access token without user elicitation. See **[Authenticate Backend Calls and Use Elicitation](../how-to-guides/security/backend-auth-and-elicitation.md#use-oauth-client-credentials)** for the operational boundaries. ### `reshapr secret list` command Lists all secrets in your organization. ```bash reshapr secret list [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr secret list ``` ```bash ID NAME TYPE DESCRIPTION 0N084D4H90937 my-secret ENDPOINT A secret to access a super secured endpoint 0NWN2WHGEA2JP 3rd-party-oauth ENDPOINT ``` ### `reshapr secret get` command Retrieves details of a specific secret by its ID. ```bash reshapr secret get [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr secret get 0N084D4H90937 ``` ```bash ℹ️ Secret details ID : 0N084D4H90937 Name : my-secret Organization: my-org Type : ENDPOINT Token : acme_wXl53oz5gFeSmBt8awTUJI72yQSrtbMP Token Header: x-acme-api-key Description : A secret to access a super secured endpoint ``` ### `reshapr secret update` command Opens an interactive editor to update a secret's properties. Immutable fields (ID, organization) are preserved automatically. ```bash reshapr secret update ``` ### `reshapr secret delete` command Deletes a secret by its ID. ```bash reshapr secret delete ``` ```bash ✅ Secret deleted successfully: 0N084D4H90937 ``` ## Configuration Plan commands ### `reshapr config list` command Lists all configuration plans in your organization. ```bash reshapr config list [options] ``` Available options: - `-s, --serviceId ` : Filter by service ID - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr config list ``` ```bash ID NAME SERVICE BACKEND API_KEY OAUTH2_CONFIG 0N4B3WS9Z6KBV github-issues-config 0N2G4YZFDD3ZF https://api.github.com/graphql Yes No ``` ### `reshapr config get` command Retrieves detailed information about a specific configuration plan. ```bash reshapr config get [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr config get 0N4B3WS9Z6KBV ``` ```bash ℹ️ Configuration plan details ID : 0N4B3WS9Z6KBV Name : github-issues-config Organization : my-org Description : GH issue-related operations config plan Service ID : 0N2G4YZFDD3ZF Backend Endpoint: https://api.github.com/graphql Backend Timeout : 30000 ms Included Ops. : ["pinIssue","createIssue"] Excluded Ops. : [] Backend Secret : No API Key : Yes OAuth2 : No ``` ### `reshapr config create` command This command proposes advanced options for configuring how your Service is to be consumed and enabling different **[Security options](../explanations/security-model.md)**. A Configuration Plan has a mandatory `name`, so this is the first argument of the command: `reshapr config create `. You can provide a more detailed description of your configuration plan goal using the `-d, --description` option in your command. The primary goal of a Configuration Plan is to integrate a Service with a backend endpoint URL, where this existing Service or API will be utilized. For that, this command has **two mandatory options**: - `-s, --serviceId ` : Allow the specification of the Service using its unique identifier - `--be, --backendEndpoint ` : Allow the specification of this Service implementation endpoint URL You can also configure a timeout for requests to the backend endpoint: - `--bt, --backendTimeout ` : Timeout in milliseconds for requests to the backend endpoint. Must be a positive number. A Configuration plan allows you to restrict the Service operations your consumer will be able to list and use. You can choose the operations you want to include or exclude in the MCP server endpoint by using the `--filter` option. Below is an illustration of the flow: ```bash reshapr config create github-issues-config -d 'GH issue-related operations config plan' -s 0N4B3WS9Z6KBV --be https://api.github.com/graphql --filter ``` The CLI will ask you if you want to proceed by choosing the operations you want to include or the operations you want to exclude: ```bash The service GitHub GraphQL has 283 operation(s) available. You can filter them to include or exclude specific operations. ? Do you want to include or exclude operations? (Use arrow keys) No ❯ Include operations Exclude operations ``` And then let you select the operations in a list: ```bash ? Select operations to include: (Press to select, to toggle all, to invert selection, and to proceed) ◯ updateProjectColumn ◯ createCheckSuite ◯ createDiscussion ❯◉ pinIssue ◯ deleteRef ❯◉ createIssue ◯ createEnvironment ◯ updateEnterpriseMembersCanMakePurchasesSetting ◯ deletePullRequestReviewComment ◯ closePullRequest ``` Instead of doing things in an interactive way using the `--filter` option, you can also use the exclusive `--includedOperations` and `--excludedOperations` as detailed below: - `--io, --includedOperations [, ]` : Allow the configuration of included operations; only the ones listed here will be actually exposed on the MCP Server endpoint. The operations must be specified within an array like this: `--io '["operation1", "operation2"]'` - `--eo, --excludedOperations [, ]` : Allow the specification of excluded operations; none of the ones listed here will be actually exposed on the MCP Server endpoint Request-header propagation can be configured on `config create` and `config create-oauth`: - `--reqhp, --requestHeaderPolicy `: Allow, deny, or rename request headers. A rename is expressed as `"Source:Target"` in the `rename` array. - `--passthrough`: Allow the incoming `Authorization` header to reach the backend. This shortcut is mutually exclusive with `--requestHeaderPolicy` and is not recommended outside bounded development or debugging. - `--reshp, --responseHeaderPolicy `: Store response rules for forward compatibility. Runtime `1.0.0` does not enforce them. ```bash reshapr config create backend-header-policy \ --serviceId '' \ --backendEndpoint 'https://api.example.com' \ --requestHeaderPolicy '{"allow":["X-Trace-Id","X-Backend-Token"],"rename":["X-Backend-Token:Authorization"]}' ``` Finally, you can use the Configuration plan to enable security options. Below are explanations of the options you may find: - `--bs, --backendSecret ` : Allow the specification of a Backend Secret to use when exposing an MCP Endpoint on gateways - `--apiKey` : Allow the generation of an API key to secure access to an MCP endpoint exposed by gateways. The MCP client should then provide this API key to the server using an `x-reshapr-key` HTTP header. The API key will be provided just once during the creation of the Configuration Plan. You must store it in a safe place. You can later use the `renew-api-key` command to revoke the existing API key and generate a new one - `--internalOAuth2` : Allows secure access to an MCP endpoint using OAuth 2 authorization backed by the *reShapr Internal OAuth Identity Provider*. This command registers an OAuth 2 Client ID dedicated to accessing future MCP endpoints for this Service. The Client ID will be provided just once during the creation of the Configuration Plan. You must store it in a safe place. You can later authenticate yourself with social providers using the `oauth2 auth-client ` command ### `reshapr config create-oauth` command This command is actually an alias of the `config create` command, but with options focused on securing access to an MCP endpoint using third-party OAuth 2 authorization servers. As such, `create-oauth` provides the same set of options for referring to a Service, the implementation backend endpoint, as well as the filtering options. It provides additional **mandatory flags** to configure the trust of access: - `--oas, --oauth2AuthorizationServers [, ]` : Allow the specification of one or many authorization server URLs that represent valid issuers for Bearer tokens, - `--oju, --oauth2jwksUri ` : Allow the configuration of the URI used for retrieving JSON Web Key Set for verifying the Bearer token signatures, - `--osc, --oauth2Scopes [, ]` : Allow the configuration of scopes that should be present in the Bearer token to allow access to the MCP endpoint. - `--osa, --oauth2StaticAudiences [, ]` : Accept additional audience values besides the dynamic Exposition URL. - `--odav, --oauth2DisableAudienceValidation` : Disable `aud` validation for compatibility. This weakens token-to-resource binding and should not be the production default. Like `config create`, it also supports the `--bt, --backendTimeout ` option to configure the backend endpoint timeout in milliseconds. Below is an example of how to create a Configuration Plan with these security options: ```bash reshapr config create-oauth 'oauth2-plan for Open-Meteo APIs' -s 0NTK2N8P7GMQR --be https://api.open-meteo.com \ --oas '["https://idp.example.com/realms/3rdparty"]' --oju https://idp.example.com/realms/3rdparty/protocol/openid-connect/certs \ --osc '["openid", "custom"]' ``` ### `reshapr config update` command Opens an interactive editor to update an existing configuration plan. Immutable fields (ID, organization) are preserved automatically. ```bash reshapr config update ``` ### `reshapr config renew-api-key` command Given a Configuration Plan that was previously created with the `--apiKey` option, this command allows you to revoke the existing API key and have reShapr generate a new one. The new API key is immediately propagated to the gateway, exposing the associated MCP Endpoint configuration. `reshapr config renew-api-key ` will remove the existing API key and output a fresh API key that replaces it. The new API key is provided only once; you must store it in a secure location and share it only with trusted individuals. ### `reshapr config duplicate` command Duplicates a Configuration Plan under a new name. ```bash reshapr config duplicate --name [options] ``` Available options: - `-n, --name ` (required) : Name of the duplicated Configuration Plan - `-o, --output ` : Output format (`json`, `yaml`) ### `reshapr config delete` command Deletes a configuration plan by its ID. This may also remove associated expositions. ```bash reshapr config delete [options] ``` Available options: - `-f, --force` : Skip confirmation prompt :::warning Deleting a configuration plan may also remove associated expositions. ::: ```bash reshapr config delete 0N4B3WS9Z6KBV ``` ```bash ? Deleting this config plan may also remove associated expositions. Are you sure you want to proceed? Yes ✅ Configuration plan 0N4B3WS9Z6KBV deleted successfully. ``` ## Exposition commands ### `reshapr expo list` command Lists all expositions in your organization. By default, only active expositions are shown. ```bash reshapr expo list [options] ``` Available options: - `-a, --all` : Display also inactive expositions - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr expo list ``` ```bash ID SERVICE BACKEND ENDPOINTS AGE 0N2G4Z0ASD034 GitHub GraphQL:20250917 https://api.github.com/graphql mcp.beta.reshapr.io 1d ``` ### `reshapr expo get` command Retrieves detailed information about a specific exposition, including its active gateway endpoints. ```bash reshapr expo get [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr expo get 0N2G4Z0ASD034 ``` ```bash ℹ️ Exposition details ID : 0N2G4Z0ASD034 Created on : 2025-09-26T14:43:37.686+00:00 Organization: my-org Service: ID : 0N2G4YZFDD3ZF Name : GitHub GraphQL Version: 20250917 Type : GRAPHQL Configuration Plan ID : 0N4B3WS9Z6KBV Name : default-plan for GitHub GraphQL BackendEndpoint: https://api.github.com/graphql Included Ops. : [] Excluded Ops. : [] Gateway Group ID : 1 Name : default Labels: {} Gateway Endpoints - ID : gw-001 Name : mcp-gateway Endpoints: mcp.beta.reshapr.io/mcp/my-org/GitHub+GraphQL/20250917 ``` ### `reshapr expo create` command Creates a new exposition by associating a Configuration Plan with a Gateway Group. ```bash reshapr expo create [options] ``` Available options: - `-c, --configuration ` (required) : Configuration Plan ID to use - `-g, --gateway-group ` (required) : Gateway Group ID to use - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr expo create -c 0N4B3WS9Z6KBV -g 1 ``` ```bash ✅ Exposition created successfully with ID: 0N2G4Z0ASD034 ``` ### `reshapr expo delete` command Deletes an exposition by its ID. ```bash reshapr expo delete ``` ```bash ✅ Exposition 0N2G4Z0ASD034 deleted successfully. ``` ## Gateway Group commands ### `reshapr gateway-group list` command Lists all Gateway Groups in your organization. ```bash reshapr gateway-group list [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr gateway-group list ``` ```bash ID ORG NAME LABELS 1 my-org default {} 0N5X2AB9CD3EF my-org staging {"env":"staging"} ``` ### `reshapr gateway-group create` command Creates a new Gateway Group with an optional set of labels for Gateway matching. ```bash reshapr gateway-group create [options] ``` Available options: - `-l, --labels ` : JSON map of key-value labels for the Gateway Group - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr gateway-group create production -l '{"env":"production","region":"eu-west-1"}' ``` ```bash ✅ Gateway group 'production' created successfully with ID: 0N7Y3CD8EF4GH ``` ### `reshapr gateway-group delete` command Deletes a Gateway Group by its ID. ```bash reshapr gateway-group delete ``` ```bash ✅ Gateway group with ID '0N7Y3CD8EF4GH' deleted successfully. ``` ## API Token commands ### `reshapr api-token list` command Lists all API tokens in your organization. API tokens are used by gateways to register and authenticate with the control plane. ```bash reshapr api-token list ``` ```bash ID NAME VALID UNTIL 0N9A4EF0GH5IJ gateway-token-1 Mon, 15 Dec 2025 00:00:00 GMT ``` ### `reshapr api-token create` command Creates a new API token for gateway registration. ```bash reshapr api-token create [options] ``` Available options: - `-v, --validity-days ` : Number of days the token is valid for (choices: `1`, `7`, `30`, `90`; defaults to `30`) ```bash reshapr api-token create my-gateway-token -v 90 ``` ```bash ⚠️ The API Token to register Gateway is: my-org-a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx ⚠️ Make sure to store it securely, as it will not be shown again. ``` ### `reshapr api-token delete` command Deletes an API token by its ID. ```bash reshapr api-token delete [options] ``` Available options: - `-f, --force` : Skip confirmation prompt :::warning Deleting an API token will prevent gateways that use it from connecting to reShapr. ::: ```bash reshapr api-token delete 0N9A4EF0GH5IJ ``` ```bash ? Deleting this API token will prevent Gateways that use it to connect to Reshapr. Are you sure you want to proceed? Yes ℹ️ API token with ID 0N9A4EF0GH5IJ deleted successfully. ``` ## Quota commands ### `reshapr quotas` command Lists and checks your reShapr quotas, showing limits and remaining capacity for each metered resource. ```bash reshapr quotas [options] ``` Available options: - `-o, --output ` : Output format (`json`, `yaml`) ```bash reshapr quotas ``` ```bash ORG METRIC ENABLED LIMIT REMAINING my-org exposition.count Y 10 8 my-org gateway-group.count Y 3 2 my-org gateway.count Y 3 2 ``` Use **[Assign and Monitor Organization Quotas](../how-to-guides/administration/organization-quotas.md)** for the administrative Web UI, CLI, and API workflows. ## Local execution commands ### `reshapr run` command Starts reShapr locally using Docker Compose (or Podman Compose). The CLI downloads the appropriate compose file from the GitHub repository for the specified release, caches it locally, and starts the containers. ```bash reshapr run [options] ``` Available options: - `-r, --release ` : Release of the containers to run (defaults to `latest`; use `nightly` for the latest development build) - `-e, --engine ` : Container engine to use (`docker` or `podman`) - `--ui` : Download and deploy the Web UI Compose add-on ```bash reshapr run ``` Example output when `latest` resolves to 0.2.3: ```text ℹ️ Resolved 'latest' to release '0.2.3'. ℹ️ Downloading compose file from https://raw.githubusercontent.com/reshaprio/reshapr/refs/tags/0.2.3/install/docker-compose-all-in-one.yml... ✅ Compose file saved to ~/.reshapr/docker-compose-0.2.3.yml ℹ️ Starting Reshapr containers (release: 0.2.3, engine: docker)... ✅ Reshapr containers started successfully. ``` To use a specific release with Podman: ```bash reshapr run -r 0.2.3 -e podman ``` To include the Web UI: ```bash reshapr run --release 0.2.3 --ui ``` ### `reshapr status` command Shows the status of locally running reShapr containers. ```bash reshapr status ``` Example output: ```text ℹ️ Reshapr containers (release: 0.2.3, engine: docker, started at: ) NAME IMAGE STATUS reshapr-control-plane :0.2.3 Up reshapr-proxy :0.2.3 Up ``` ### `reshapr stop` command Stops locally running reShapr containers and removes the run state. ```bash reshapr stop ``` Example output: ```text ℹ️ Stopping Reshapr containers (release: 0.2.3, engine: docker)... ✅ Reshapr containers stopped successfully. ``` ## Administration commands The `reshapr admin` command manages control-plane users, organizations, quotas, memberships, and service accounts. These commands require a deployment admin API key and do not require a normal user login. ```bash reshapr admin [--admin-api-key ] [--server ] ``` Prefer the `RESHAPR_ADMIN_API_KEY` environment variable to placing the key in shell history. See **[Manage Organizations, Owners, and Memberships](../how-to-guides/administration/organizations-and-memberships.md)** and **[Assign and Monitor Organization Quotas](../how-to-guides/administration/organization-quotas.md)** for applied workflows, the **[Admin CLI guide](https://github.com/reshaprio/reshapr/blob/main/cli/ADMIN_CLI.md)** for the current subcommands and examples, or run `reshapr admin --help`. Database encryption commands report the active key and re-encrypt stored sensitive fields with that key: ```bash reshapr admin encryption status reshapr admin encryption rotate --yes ``` Rotation is idempotent and reports the numbers of Secret and Configuration Plan values changed. Use **[Upgrade reShapr and Rotate Runtime Secrets](../how-to-guides/operations/upgrade-and-rotate.md#rotate-the-database-encryption-key)** for the required key rollout and verification order. ## Shell completion Release `1.0.0` provides generated shell completion through `reshapr completion`. Run `reshapr completion --help` to select and install the script for the current shell; completion itself does not require an authenticated session. ## Structured output Most of the CLI commands allow a `--output ` option (or just `-o` for the short alternative) that allows you to format the output in either `json` or `yaml`. Using this flag option is extremely convenient, combined with utilities such as `jq` or `yq`, for automating publication or configuration changes on reShapr. Below is an example: ```bash reshapr service list -o json ``` ```bash [ { "id": "0N0802V07SZEH", "organizationId": "reshapr", "name": "Open-Meteo APIs", "version": "1.0", "createdOn": "2025-09-19T14:35:58.591+00:00", "type": "REST" } ] ``` ```bash reshapr service get 0N0802V07SZEH -o json | jq .name ``` ```bash "Open-Meteo APIs" ``` --- ## Custom Tools An imported API operation does not always represent the task an Agent needs to perform. It may expose low-level parameters, protocol-specific navigation, or a capability broader than the intended use case. A `CustomTools` Artifact defines a smaller task-oriented interface while retaining the existing API implementation. Use **[Context Control](../explanations/context-control.md)** to decide when operation selection is sufficient and when a Custom Tool is the better mechanism. **[Context Control in Practice](../tutorials/context-control-in-practice.md)** provides a complete, measured example. reShapr provides **an easy way to design and specify your Custom Tools using a simple YAML description,** called the `CustomTools` specification. If you want to provide such custom tools to your reShapr-powered MCP endpoint, you’ll need to write this simple file and `attach` it to your existing Service. Let’s explain this concept via a simple example: we want to provide an MCP Tool that fetches details on a GitHub user. The official MCP Server is a no-brainer as it provides too many high-risk operations, so we decided to produce our own reShapr-powered one, reusing the GitHub GraphQL API and reducing the surface to only the existing `user` operation. You can do this in reshapr using this command: ```bash reshapr import -f ../dev/github-api.graphql --sn 'GitHub GraphQL' --sv '20250917' --be https://api.github.com/graphql --io '["user"]' ``` That produces an MCP endpoint with only the `user` operation, but the generated Tool still reflects the GraphQL API's parameters and relation navigation. A task-specific Tool can make the expected input and selection explicit. Let’s say we want default information on the user, but also its avatar and details on its latest followers… We can define a new `get_user_with_latest_followers(login)` tool for a specific use case, and we just have to create and attach this simple YAML file: ```yaml apiVersion: reshapr.io/v1alpha1 kind: CustomTools service: name: GitHub GraphQL version: '20250917' customTools: get_user_with_latest_followers: tool: user description: Get a user details with the latest followers details input: type: object properties: user: type: string description: The GitHub login of the user to fetch required: - user arguments: login: ${user} __relation_avatarUrl: size: 32 __relation_followers: last: 10 ``` A `CustomTools` artifact follows some simple rules: - It always contains an identification section made of `apiVersion` and `kind` properties that **must** have the **`reshapr.io/v1alpha1`** and `CustomTools` values respectively, - It **must** be bound to a specific reShapr **[Service](../explanations/services-and-artifacts.md)** using the **`service.name`** and `service.version` properties whose values **must** match an already discovered Service, - The `customTools` section then defines the tools: - We have a single tool here: `get_user_with_latest_followers` - A declarative custom tool **must** have a `tool` that defines the original tool it overrides and replaces: here we’re using the GitHub `user` tool, - A custom tool **may** provide optional `title` and `description` to provide more context to the LLM or Agent when choosing an appropriate tool, - A custom tool **must** also provide an `input` schema description that describes its parameters. Input schema reuses the same structure as the regular MCP Tools Input Schema. - A declarative custom tool **may** also specify `arguments` that represent the arguments that will be used with the original tool that is overridden. Here we’re fixing the arguments as well as the relation navigation options for fetching exactly what we need. When attached, reShapr derives each `customTools` key as an Artifact capability. A Configuration Plan includes the Artifact by name through `includedArtifacts`; see **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)**. In the case of custom tools using `arguments`, the value **can** be expressed using `${}` expressions that will be replaced by input values. Typically in our example, the MCP client will send a `user` value as input, and this value will be used in the place of the `${user}` placeholder when invoking the original tool. ## Scripted Custom Tools Available since reShapr `0.0.14`, a Custom Tool can also define its behavior with a JavaScript `script`. This is useful when a single business action needs to orchestrate several existing tools, possibly from different Services of the same organization, and return a compact result that is easier for an Agent to use. Use **[Build a Scripted Custom Tool](../how-to-guides/build-scripted-custom-tool.md)** for an end-to-end procedure covering cross-Service calls, asynchronous orchestration, failures, and runtime guardrails. A custom tool item is now **either** declarative **or** scripted: | Form | Main fields | Purpose | | --- | --- | --- | | Declarative | `tool`, `arguments` | Map the custom tool to one backend tool, with templated arguments. | | Scripted | `script`, `tools` | Run JavaScript logic that may call several tools and reshape their results. | Both forms still require `description` and `input`. The `input` JSON Schema defines the parameters exposed to the MCP client and becomes available inside the script as the `input` constant. A scripted custom tool never talks directly to backend endpoints. It calls other reShapr tools through the `rs` host API. This means the usual reShapr behavior still applies to every underlying call: security, backend secrets, elicitation handling, output filtering, audit, and distributed tracing. ### Scripted tool fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `description` | string | yes | Human-readable description of the custom tool. | | `input` | object | yes | JSON Schema object describing the tool parameters. | | `script` | string | yes | JavaScript body to execute. It must `return` a JSON-serializable value. | | `tools` | array | yes | Allow-list of tools the script may call. | The `tools` array is both a security allow-list and the input used by reShapr to prepare elicitation flows before the script runs. Each item contains a `tool` name and, when calling another Service, an optional `service` value using the readable `` form. ```yaml tools: - tool: user - service: "Issues API:1.0.0" tool: listIssues ``` When `service` is omitted, the script calls a tool from the same Service as the Custom Tool. Cross-Service calls are restricted to Services belonging to the same organization. A declared script dependency remains callable even when that underlying operation is excluded from the client-visible Configuration Plan surface. This allows a scripted Custom Tool to expose a bounded business action while hiding its lower-level operations. The `tools` allow-list still gates every internal call; operation inclusion and exclusion are exposure controls, not backend authorization rules. ### The `rs` host API Inside the script, reShapr exposes a global `rs` object: | Function | Description | | --- | --- | | `rs.callTool(tool, params)` | Synchronously call a tool on the same Service. | | `rs.callTool(service, tool, params)` | Synchronously call a tool on another Service. | | `rs.callToolAsync(tool, params)` | Start a same-Service call without blocking. | | `rs.callToolAsync(service, tool, params)` | Start a cross-Service call without blocking. | | `rs.awaitPromises([p1, p2])` | Wait for async calls and return the results in the same order. | | `rs.fail(message, data)` | Fail the whole Custom Tool with a structured MCP error. | Every call returns a result object: ```js { ok: true, content: {}, error: null } ``` If a call fails, `ok` is `false`, `content` is `null`, and `error` contains the failure details. A script should check `result.ok` before reading `result.content`. ### Returning results and failures A script can return any JSON-serializable value: ```js const result = rs.callTool('user', { login: input.user }); if (!result.ok) { throw new Error('Could not fetch user ' + input.user); } return { login: result.content.login }; ``` Returning a value makes the Custom Tool call succeed. Throwing an error makes the Custom Tool call fail with an MCP tool error. For machine-readable failures, use `rs.fail(message, data)`: ```js rs.fail('GitHub rate limit exceeded', { retryAfter: 60, scope: 'graphql' }); ``` This returns an MCP error whose content is a structured JSON object containing the `message` and `data` fields. ### Asynchronous orchestration Use `rs.callToolAsync(...)` when several tool calls can run in parallel. The calls start immediately, and `rs.awaitPromises(...)` waits for them: ```js const first = rs.callToolAsync('user', { login: input.firstUser }); const second = rs.callToolAsync('user', { login: input.secondUser }); const results = rs.awaitPromises([first, second]); return { users: results.map(function (result) { return result.ok ? result.content : { error: result.error }; }) }; ``` Partial failures of asynchronous calls are not thrown automatically. They are returned as `ok: false` result objects, so the script can decide whether to ignore, recover, or fail the whole Custom Tool. ### Backend secrets and elicitation Before a scripted Custom Tool runs, reShapr checks every tool declared in `tools`. If one of these target tools requires an elicitation-based backend secret that is not yet available for the current MCP session, the MCP Server returns the elicitation request instead of starting the script. Once all required secrets are resolved, the script runs normally. This is why the `tools` list must be exhaustive: it lets reShapr know which backend credentials may be required before any JavaScript code is executed. ### Guardrails Script execution is bounded by gateway settings: | Setting | Default | Description | | --- | --- | --- | | `reshapr.gateway.scripting.timeout` | `10000` ms | Maximum script execution time. `0` disables the timeout. | | `reshapr.gateway.scripting.max-tool-calls` | `10` | Maximum number of tool calls per script execution. | | `reshapr.gateway.scripting.max-depth` | `5` | Maximum nesting depth when a scripted tool calls another scripted tool. | The timeout cancels interruptible work such as backend calls and waits. Keep scripts simple and avoid unbounded CPU loops. Artifact validation rejects a declarative tool without `arguments`, a placeholder that is absent from `input.properties`, and a reference to a Tool that cannot be resolved. Compilation failures are returned without exposing the generated wrapper source. ### Example Here is a scripted Custom Tool that fetches two GitHub users in parallel and returns a small side-by-side comparison: ```yaml apiVersion: reshapr.io/v1alpha1 kind: CustomTools service: name: GitHub GraphQL version: '20250917' customTools: compare_two_users: description: Fetch two GitHub users in parallel and compare their profiles. input: type: object properties: firstUser: type: string secondUser: type: string required: - firstUser - secondUser tools: - tool: user script: | function summarize(result, login) { if (!result.ok) { return { login: login, error: result.error }; } const user = result.content.data && result.content.data.user ? result.content.data.user : result.content.user || result.content; return { login: user.login || login, name: user.name, company: user.company }; } const first = rs.callToolAsync('user', { login: input.firstUser }); const second = rs.callToolAsync('user', { login: input.secondUser }); const results = rs.awaitPromises([first, second]); return { users: [ summarize(results[0], input.firstUser), summarize(results[1], input.secondUser) ] }; ``` --- ## reShapr features `Available with limits` means that the capability requires configuration or has a narrower scope than its general name might suggest. Links point to the owning reference when implementation details change frequently. ## API translation and Context Control | Capability | Availability | Scope | Reference | |---|---|---|---| | OpenAPI import | Available | OpenAPI 3.x documents are converted into REST-backed MCP Tools. Swagger/OpenAPI 2.x import is not provided. | [Services and Artifacts](../explanations/services-and-artifacts.md) | | GraphQL import | Available | Queries and mutations become Tools. Service name and version must be supplied when the schema does not provide them. | [Services and Artifacts](../explanations/services-and-artifacts.md) | | Protobuf/gRPC import | Available | Protobuf 3 services become Tools backed by gRPC. Imports are resolves before compilation. | [Services and Artifacts](../explanations/services-and-artifacts.md) | | Import by file or URL | Available | Both paths support service name and version overrides. URL imports can resolve external dependencies that are unavailable to a local file import. | [CLI commands](cli-commands.md) | | Operation selection | Available | A Configuration Plan can include or exclude API operations; inclusion takes precedence. | [Configuration Plan and Exposition](../explanations/configuration-and-exposition.md) | | reShapr artifacts | Available | Prompts, Resources, Custom Tools, and Tools Output Filters can be attached to a Service and selected per Configuration Plan. | [Configuration Plan and Exposition](../explanations/configuration-and-exposition.md) | | Declarative Custom Tools | Available | Rename, condense, or reshape existing operations without creating a new backend. | [Custom Tools specification](custom-tools-specification.md) | | Scripted Custom Tools | Available with limits | JavaScript orchestration can call allowed Tools with bounded execution time and depth. This extension is programmable rather than no-code. | [Custom Tools specification](custom-tools-specification.md#scripted-custom-tools) | | Output filtering and TOON | Available | Gateway-side rules can retain fields, apply JSON Patch, compact JSON, or encode JSON output as TOON before returning it to the MCP client. | [Tools Output Filtering](spec-outtools-filtering.md) | ## MCP protocol | Capability | Availability | Scope | Reference | |---|---|---|---| | Protocol versions | Available | `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25`, and the public `2026-07-28` version are negotiated by the Gateway. | [MCP Support Matrix](./mcp-support.md) | | Streamable HTTP | Available | Expositions provide MCP endpoints over HTTP; TLS termination depends on the deployment. WebSocket transport is not provided. | [Configuration Plan and Exposition](../explanations/configuration-and-exposition.md) | | Session and stateless modes | Available | Versions before `2026-07-28` use a server-issued session ID. `2026-07-28` uses stateless requests and `server/discover`. | [MCP Compatibility](../explanations/mcp-compatibility.md) | | Tools | Available | `tools/list` and `tools/call` dispatch to REST, GraphQL, gRPC, or Custom Tools selected by the Plan. | [Custom Tools specification](custom-tools-specification.md) | | Prompts | Available | `prompts/list` and `prompts/get` serve Prompts artifacts selected by the Plan. | [Prompts specification](prompts-specification.md) | | Resources | Available | Static and templated Resources support list, template list, and read operations. | [Resources specification](resources-specification.md) | | URL elicitation | Available with limits | Backend credentials can be requested through legacy session-bound errors or `2026-07-28` stateless elicitation bound to an authenticated user. | [Backend authentication and elicitation](../how-to-guides/security/backend-auth-and-elicitation.md) | | Client cache hints | Available with limits | `ttlMs` and `cacheScope` are returned only for the `2026-07-28` protocol shape. | [Configuration Plan and Exposition](../explanations/configuration-and-exposition.md) | Methods such as roots, sampling, and subscriptions are not exposed as server capabilities. The **[MCP Support Matrix](./mcp-support.md)** distinguishes implemented, removed, and unimplemented methods. ## Security and governance | Capability | Availability | Scope | Reference | |---|---|---|---| | MCP endpoint API key | Available | The proxy validates `x-reshapr-key`; keys can be renewed and propagated to connected proxies. | [API key guide](../how-to-guides/security/api-key.md) | | MCP endpoint OAuth 2.0 | Available | The proxy validates signed bearer JWTs against configured issuers, JWKS, Exposition scopes, and dynamic or static audiences, and publishes RFC 9728 metadata. | [OAuth 2.0 guide](../how-to-guides/security/oauth.md) | | Backend authentication | Available with limits | Basic, token/header, certificate, OAuth Client Credentials, and elicited OAuth credentials depend on the backend protocol and Secret configuration. | [Backend authentication and elicitation](../how-to-guides/security/backend-auth-and-elicitation.md) | | Backend request header policy | Available with limits | A Configuration Plan can allow, deny, or rename request headers sent to HTTP backends or converted to gRPC call metadata. Response rules are reserved and not enforced in `1.0.0`. | [Configure backend request header policy](../how-to-guides/security/configure-backend-header-policy.md) | | Database encryption and key rotation | Available with limits | The control plane uses identified AES-256-GCM keys and can re-encrypt stored sensitive values with the active key through an administrator command. Key rollout and invocation remain operator-managed. | [Upgrade and rotate runtime secrets](../how-to-guides/operations/upgrade-and-rotate.md) | | Local secret references | Available with limits | Hybrid proxies resolve `${env:VARIABLE}` references locally on each backend call. `env` is the provided resolver. | [Backend authentication and elicitation](../how-to-guides/security/backend-auth-and-elicitation.md#create-a-locally-resolved-secret) | | Audit events | Available with limits | A proxy emits structured MCP-call and authentication-failure events when audit is enabled on the Configuration Plan. | [Audit MCP endpoint calls](../how-to-guides/audit-mcp-endpoint.md) | | Multi-tenancy | Available | Control-plane data is isolated by organization through application-level discriminator tenancy. | [Multi-tenancy and Administrative Governance](../explanations/multi-tenancy-administrative-governance.md) | | Organization quotas | Available with limits | Quotas limit governance resources such as Expositions and Gateways. They are not request-rate limits. | [Assign and monitor quotas](../how-to-guides/administration/organization-quotas.md) and [governance model](../explanations/multi-tenancy-administrative-governance.md#quotas-govern-resource-counts) | | Administrative identities | Available | Users, organizations, memberships, service accounts, and Gateway API tokens are managed through dedicated control-plane surfaces. | [Manage organizations and memberships](../how-to-guides/administration/organizations-and-memberships.md) and [governance model](../explanations/multi-tenancy-administrative-governance.md#keep-identity-types-separate) | ## Product interfaces | Capability | Availability | Scope | Reference | |---|---|---|---| | Public and administrative APIs | Available | OpenAPI contracts cover product resources, authentication, and administration. | [reShapr API contracts](https://github.com/reshaprio/reshapr) | | CLI | Available | The CLI covers login, import, Services, artifacts, Secrets, Plans, Expositions, Gateway Groups, tokens, quotas, administrative workflows, and generated shell completion. | [CLI reference](cli-commands.md) and [CI/CD automation](../how-to-guides/automate-with-cli-in-cicd.md) | | Web UI | Available | The Web UI covers the main import-to-Exposition workflow and organization administration. | [Web UI](https://github.com/reshaprio/reshapr/tree/main/web-ui) | | Live configuration propagation | Available | Configuration events are streamed to connected proxies without requiring a proxy restart. This is not a general zero-downtime or rollback guarantee. | [Control Plane to Proxy Synchronization](../explanations/control-plane-gateway-synchronization.md) | | Proxy observability | Available with limits | The proxy can export OpenTelemetry traces, metrics, and logs; audit events remain conditional. Equivalent coverage is not provided across every component. | [Observe the reShapr Proxy](../how-to-guides/operations/observe-and-audit.md) | ## Deployment and Kubernetes | Capability | Availability | Scope | Reference | |---|---|---|---| | Local runtime | Available | The CLI starts and stops a release Docker Compose stack with Docker or Podman and can include the Web UI. | [reShapr project](https://github.com/reshaprio/reshapr) | | Kubernetes APIs and operator | Available with limits | Seven `v1alpha1` CRDs manage Services, Plans, Expositions, Gateway Groups, Secret sources, Custom Tools, and Resources. Custom Tools and Resources do not currently clean up remote artifacts on deletion. | [Controller documentation](https://github.com/reshaprio/reshapr-controllers/tree/main/documentation) | | Sidecar injection | Available with limits | The admission controller injects Gateway sidecars and creates discovery/MCP Services for supported Deployment-owned workloads. It is fail-open by default. | [Admission controller](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/admission-controller.md) | | Helm packaging | Available | Four OCI charts package the control plane, proxy, Web UI, and controllers. | [Helm charts](https://github.com/reshaprio/reshapr-helm-charts) | | PostgreSQL topology | Available with limits | The control-plane chart supports bundled or external PostgreSQL. The bundled development database is not a high-availability setup. | [Control-plane chart](https://github.com/reshaprio/reshapr-helm-charts/tree/main/control-plane) | | Runtime scaling and availability | Available with limits | Production profiles configure replicas and PDBs for the control plane and Web UI; the proxy adds clustering and HPA. End-to-end availability still depends on PostgreSQL and the target infrastructure. | [Helm charts](https://github.com/reshaprio/reshapr-helm-charts) | | Network and metrics integration | Available with limits | The proxy chart provides optional NetworkPolicy and ServiceMonitor resources. Coverage is not uniform across all four charts. | [Proxy chart](https://github.com/reshaprio/reshapr-helm-charts/tree/main/proxy) | | Installation profiles and signatures | Available | All charts provide development and production values; published charts are signed with Cosign. | [Helm chart verification](https://github.com/reshaprio/reshapr-helm-charts#verifying-chart-signatures) | | Upgrades | Available with limits | Kubernetes rolling updates, startup database migrations, retained clustering secrets, retained CRDs, and administrator-triggered database key rotation support upgrades. Automated rollback and general credential rotation are not provided. | [Upgrade and rotate runtime secrets](../how-to-guides/operations/upgrade-and-rotate.md) | --- ## Helm Charts Overview reShapr components are packaged as four independently versioned Helm charts. This page describes baseline `0.0.14`; the [`reshapr-helm-charts`](https://github.com/reshaprio/reshapr-helm-charts) repository owns installation commands, values, and release artifacts. ## Prerequisites The current charts declare Kubernetes 1.25 or later and require Helm 3.8 or later for OCI support. Installing the controllers also requires cluster-level permissions for CRDs and RBAC. `cert-manager` is required when the Web UI uses TLS and when the admission controller uses its default certificate provider. The controllers chart also documents OpenShift and existing-certificate alternatives. ## Choose a chart | Chart | Role | Typical placement | Canonical documentation | |---|---|---|---| | `reshapr-control-plane` | Control plane API and configuration management | Core platform namespace | [README](https://github.com/reshaprio/reshapr-helm-charts/blob/main/control-plane/README.md) and [commands](https://github.com/reshaprio/reshapr-helm-charts/blob/main/control-plane/COMMANDS.md) | | `reshapr-proxy` | MCP server and data plane | Dedicated proxy namespace or close to workloads | [README](https://github.com/reshaprio/reshapr-helm-charts/blob/main/proxy/README.md) and [commands](https://github.com/reshaprio/reshapr-helm-charts/blob/main/proxy/COMMANDS.md) | | `reshapr-web-ui` | Administration dashboard | Platform namespace, connected to the control plane | [README](https://github.com/reshaprio/reshapr-helm-charts/blob/main/web-ui/README.md) and [commands](https://github.com/reshaprio/reshapr-helm-charts/blob/main/web-ui/COMMANDS.md) | | `reshapr-controllers` | Kubernetes operator and admission webhook | `reshapr-system` by default | [README](https://github.com/reshaprio/reshapr-helm-charts/blob/main/controllers/README.md) and [commands](https://github.com/reshaprio/reshapr-helm-charts/blob/main/controllers/COMMANDS.md) | ## Topology choices ### Core platform Install the control-plane chart as the platform anchor. It can provision PostgreSQL and the Web UI as optional subcharts, or connect to separately managed instances. Add one or more proxy releases for MCP traffic. ### Standalone components Install the proxy or Web UI chart independently when their lifecycle, namespace, or scaling must remain separate from the control plane. Both still require connectivity to a running control plane. ### Kubernetes-native management Install the controllers chart to manage reShapr resources through Kubernetes APIs or inject proxy sidecars through the admission webhook. The operator and admission controller are enabled together by default and can be selected independently. See **[Kubernetes APIs and Controllers](./kubernetes-apis.md)** before choosing this path. ## Development and production values Each chart directory owns its `values.yaml`, `values-dev.yaml`, and `values-production.yaml`. Use those files as the current source for image tags, resources, replicas, ingress, security contexts, and component-specific settings: - [Control plane values](https://github.com/reshaprio/reshapr-helm-charts/blob/main/control-plane/values.yaml) - [Proxy values](https://github.com/reshaprio/reshapr-helm-charts/blob/main/proxy/values.yaml) - [Web UI values](https://github.com/reshaprio/reshapr-helm-charts/blob/main/web-ui/values.yaml) - [Controllers values](https://github.com/reshaprio/reshapr-helm-charts/blob/main/controllers/values.yaml) For a reproducible installation, select an immutable version from the [Helm chart releases](https://github.com/reshaprio/reshapr-helm-charts/releases) and use documentation and values from the same tag. Charts `0.0.14` retain the control-plane `encryptionKey.activeKeyId` and `encryptionKey.keys.` values required for AES-256-GCM key rotation. They retain `encryptionKey.value` and `encryptionKey.key` only for decrypting legacy AES/ECB values during migration. The Web UI chart sets `BODY_SIZE_LIMIT` to `12M` and allows an explicit override through `extraEnv`. The controllers chart `0.0.14` packages the controllers `0.0.3` CRDs, including the plural `Resources` kind. Its default values still select `nightly`, while `Chart.yaml` and installation notes report app version `0.0.1`. Pin both controller image tags to `0.0.3` for the reviewed baseline; do not use chart metadata alone to identify the running version. ## Limits - Chart versions are not necessarily identical across all four components; verify each selected release. - Controllers chart `0.0.14` packages the baseline CRDs but does not select the baseline controller image without explicit overrides. - Helm does not remove CRDs when the controllers chart is uninstalled. Deleting CRDs manually also deletes their custom resources across namespaces. - This overview does not duplicate the complete values schema or installation commands. Review the selected chart's owner documentation before deployment. ## Next step - Use **[Deploy reShapr on Kubernetes for Production](../how-to-guides/deploy-kubernetes-production.md)** to install all four charts with production-oriented settings. - Use **[Product Interfaces](./interfaces.md)** to find API and CLI contracts. - Use **[Kubernetes APIs and Controllers](./kubernetes-apis.md)** to choose CRDs or sidecar injection. --- ## Product Interfaces Use this hub to find the canonical contract or implementation for each reShapr interface. The linked owner repositories carry exhaustive and frequently changing details; this site provides orientation and bounded examples. ## Stability conventions - **Current contract** links point to the owner's default branch and evolve with the product. - **Release-specific usage** should point to a Git tag or release matching the version used by an executable guide. - **Generated contracts** remain owned by their source repository and should not be copied into this site. ## Control plane APIs The [`reshapr`](https://github.com/reshaprio/reshapr) repository owns the control plane contracts. | Interface | Purpose | Current contract | |---|---|---| | Public API | Manage Services, artifacts, Configuration Plans, Expositions, Secrets, Gateway Groups, and API tokens | [Public OpenAPI](https://github.com/reshaprio/reshapr/blob/main/reshapr-public-openapi-v0.1.yaml) | | Administration API | Manage users, organizations, memberships, quotas, and service accounts | [Admin OpenAPI](https://github.com/reshaprio/reshapr/blob/main/reshapr-admin-ctrl-openapi-v0.1.yaml), [organization guide](../how-to-guides/administration/organizations-and-memberships.md), and [quota guide](../how-to-guides/administration/organization-quotas.md) | | Authentication API | Authenticate users and establish CLI or browser sessions | [Authentication OpenAPI](https://github.com/reshaprio/reshapr/blob/main/reshapr-authentication-openapi-v0.1.yaml) | ## MCP interface - **[MCP Compatibility](../explanations/mcp-compatibility.md)** explains session-based and stateless negotiation, state, response dialects, and elicitation. - **[MCP Support Matrix](./mcp-support.md)** records the exact versions and server methods verified for reShapr `1.0.0`. - **[MCP implementation](https://github.com/reshaprio/reshapr/tree/main/proxy/src/main/java/io/reshapr/proxy/mcp)** is the current owner for Gateway protocol behavior. - **[Official MCP specification](https://modelcontextprotocol.io/specification/)** owns the protocol beyond reShapr's implementation boundary. ## Command-line interfaces - **[CLI reference](./cli-commands.md)** provides the user-facing command index and bounded examples on this site. - **[Artifact import guide](../how-to-guides/import-api-artifacts.md)** applies the Web UI and CLI to OpenAPI, GraphQL, and Protobuf sources. - **[CLI source](https://github.com/reshaprio/reshapr/tree/main/cli)** is the current owner for registered commands and embedded help. - **[Admin CLI guide](https://github.com/reshaprio/reshapr/blob/main/cli/ADMIN_CLI.md)** owns exhaustive administration workflows. - **[Organization and membership guide](../how-to-guides/administration/organizations-and-memberships.md)** covers organization creation, ownership, and user access. - **[Organization quota guide](../how-to-guides/administration/organization-quotas.md)** applies the Web UI, CLI, and administration API to one bounded governance task. Use `reshapr --help` or `reshapr --help` for the command set installed on your machine. ## Kubernetes APIs - **[Kubernetes APIs and Controllers](./kubernetes-apis.md)** orients readers across the operator, admission webhook, and seven custom resources. - **[Controllers documentation](https://github.com/reshaprio/reshapr-controllers/tree/main/documentation)** owns installation and per-resource references. - **[Generated CRD schemas](https://github.com/reshaprio/reshapr-controllers/tree/main/deploy/crd)** are the current Kubernetes contracts. ## Helm interfaces - **[Helm Charts Overview](./helm-charts.md)** helps readers choose among the four reShapr charts. - **[Helm charts repository](https://github.com/reshaprio/reshapr-helm-charts)** owns chart documentation, commands, and values. - **[Helm releases](https://github.com/reshaprio/reshapr-helm-charts/releases)** provide immutable versions for executable deployment procedures. Consult each chart's `README.md`, `COMMANDS.md`, and `values.yaml` in the owner repository rather than relying on copied option tables. --- ## Kubernetes APIs and Controllers Overview The [`reshapr-controllers`](https://github.com/reshaprio/reshapr-controllers) repository provides a Kubernetes operator, seven namespaced custom resources, and an admission webhook for proxy sidecar injection. The [0.0.3 documentation index](https://github.com/reshaprio/reshapr-controllers/tree/0.0.3/documentation) and [generated CRDs](https://github.com/reshaprio/reshapr-controllers/tree/0.0.3/deploy/crd) are the canonical references for this baseline. ## Operator model The operator reconciles Kubernetes resources against a reachable reShapr control plane. Resources use the `reshapr.io/v1alpha1` API and identify their target through `reshapr.io/instance` and `reshapr.io/organization` annotations. Reconciliation progress and control-plane identifiers are reported in each resource's status. The operator ServiceAccount must be registered as a trusted control-plane client. See the canonical [instance connection flow](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/instance-connection.md) and [operator installation](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/installation-operator.md). ## Custom resources | Kind | Role and dependencies | Deletion behavior | Canonical reference | |---|---|---|---| | `Service` | Imports the primary OpenAPI, GraphQL, or Protobuf artifact | Remote Service cleanup is enabled by default; `keepOnDelete` can retain it | [Service CR](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/service-cr.md) | | `GatewayGroup` | Declares the labels used to select Gateways | Remote Gateway Group cleanup is enabled by default; `keepOnDelete` can retain it | [GatewayGroup CR](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/gatewaygroup-cr.md) | | `ConfigurationPlan` | Binds an existing Service to a backend endpoint, operation and artifact selection, cache settings, request-header policy, and security configuration | The reconciler cleans up its remote Configuration Plan | [ConfigurationPlan CR](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/configurationplan-cr.md) | | `Exposition` | Exposes a Service through a ready Configuration Plan and Gateway Group | Remote Exposition cleanup is enabled by default; `keepOnDelete` can retain it | [Exposition CR](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/exposition-cr.md) | | `SecretSource` | Declares control-plane Secrets, optionally sourced from Kubernetes Secrets | Remote Secret cleanup is enabled by default; `keepOnDelete` can retain it | [SecretSource CR](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/secretsource-cr.md) | | `CustomTools` | Attaches declarative or scripted tools to an existing Service | No remote artifact cleanup is implemented when the CR is deleted | [CustomTools CR](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/customtools-cr.md) | | `Resources` | Declares MCP resources and resource templates for an existing Service | No remote artifact cleanup is implemented when the CR is deleted | [Resources CR](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/resources-cr.md) | There are no dedicated `Prompts` or `ToolsOutputFilters` CRDs in the current API set. Controllers `0.0.3` expose `spec.headerPolicy.request` and `spec.headerPolicy.response` with `allow`, `deny`, and `rename` rules. The operator forwards both shapes to the control plane, but runtime `1.0.0` enforces only request rules; response rules are reserved for future use. ## Admission controller The mutating admission webhook injects a reShapr proxy sidecar into Pods annotated with `io.reshapr/inject: "true"`. For workloads owned by a Deployment, its controller can also create the headless clustering Service and the MCP Service used to reach injected proxies. The webhook is fail-open by default through `failurePolicy: Ignore`. Its serving endpoint requires TLS; the controllers chart supports cert-manager, OpenShift service certificates, or an existing certificate. See the canonical [admission controller](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/admission-controller.md) and [installation guide](https://github.com/reshaprio/reshapr-controllers/blob/main/documentation/installation-admission.md). ## Limits - `CustomTools` and `Resources` deletion can leave their remote artifacts in the target Service. - Controllers `0.0.3` upload both `CustomTools` and `Resources` artifacts as `artifact.json`; reconciling both kinds for one Service can replace the previously attached artifact. - Reading Kubernetes Secrets for `SecretSource` requires the operator's separate Secret-reader RBAC. - The admission controller creates supporting Services only for injected Pods owned by a Deployment. - Controller-specific metrics and traces are not currently documented as a supported observability surface; rely on component logs unless the owner documentation states otherwise. ## Next step - Use **[Your First GitOps-managed MCP Endpoint](../tutorials/first-gitops-mcp-endpoint.md)** to create and verify an endpoint from these APIs. - Use **[Manage reShapr Resources with GitOps](../how-to-guides/manage-resources-with-gitops.md)** to operate their update and deletion lifecycle. - Use **[Helm Charts Overview](./helm-charts.md)** to choose the controllers chart and its dependencies. - Use **[Product Interfaces](./interfaces.md)** to find the remaining reShapr contracts. --- ## MCP Support Matrix This reference records the MCP server behavior implemented by the reShapr `1.0.0` Gateway. It distinguishes protocol vocabulary declared in code from methods that the server dispatcher actually handles. For the reasoning behind the two modes, see **[MCP Compatibility: Session and Stateless Modes](../explanations/mcp-compatibility.md)**. For executable requests, see **[Test an MCP Endpoint](../how-to-guides/test-mcp-endpoint.md)**. ## Protocol versions and modes | Protocol version | Mode | First request | State on later requests | Result dialect | Backend credential elicitation | Client cache hints | |---|---|---|---|---|---|---| | `2024-11-05` | Session-based | `initialize` | `MCP-Session-Id` | Legacy | `URL_ELICITATION_REQUIRED`, bound to the session | No | | `2025-03-26` | Session-based | `initialize` | `MCP-Session-Id` | Legacy | `URL_ELICITATION_REQUIRED`, bound to the session | No | | `2025-06-18` | Session-based | `initialize` | `MCP-Session-Id` | Legacy | `URL_ELICITATION_REQUIRED`, bound to the session | No | | `2025-11-25` | Session-based | `initialize` | `MCP-Session-Id` | Legacy | `URL_ELICITATION_REQUIRED`, bound to the session | No | | `2026-07-28` | Stateless | `server/discover` | Protocol header and request metadata; no session ID | Modern | `input_required` with `elicitation/create`, bound to authenticated `iss` and `sub` | `ttlMs` and `cacheScope` | `2026-07-28` is a public protocol version supported by reShapr. Versions not listed here are not accepted merely because they sort after a supported date. ## Implemented server methods | Area | Method | Session-based versions | Stateless `2026-07-28` | Notes | |---|---|---:|---:|---| | Lifecycle | `initialize` | Yes | Removed | Negotiates a historical version and creates a session. Modern requests receive HTTP `404` and JSON-RPC `-32601`. | | Discovery | `server/discover` | Not the session entry point | Yes | Returns server information, capabilities, and supported versions for stateless clients. | | Tools | `tools/list` | Yes | Yes | Lists operations selected by the Configuration Plan. | | Tools | `tools/call` | Yes | Yes | Dispatches REST, GraphQL, gRPC, or Custom Tool execution. | | Prompts | `prompts/list` | Yes | Yes | Lists selected Prompt Artifacts. | | Prompts | `prompts/get` | Yes | Yes | Resolves a Prompt and its declared arguments. | | Resources | `resources/list` | Yes | Yes | Lists selected static and templated Resources. | | Resources | `resources/templates/list` | Yes | Yes | Lists Resource templates. | | Resources | `resources/read` | Yes | Yes | Reads a static Resource or resolves a Resource template. | These nine methods are the cases handled by the `McpController` dispatcher. Notifications sent by a client and requests sent by the server are not additional server endpoints in this table. ## Declared but unavailable server methods | Method or area | Session-based versions | Stateless `2026-07-28` | Classification | |---|---:|---:|---| | `ping` | Not dispatched | Removed | Declared lifecycle method, not an implemented reShapr server method. | | `logging/setLevel` | Not dispatched | Removed | Removed from the modern revision and not dispatched for historical sessions. | | `resources/subscribe` | Not dispatched | Removed | Resource subscriptions are not implemented. | | `resources/unsubscribe` | Not dispatched | Removed | Resource subscriptions are not implemented. | | `roots/list` | Not dispatched | Not dispatched | A schema constant does not establish server support. | | `sampling/createMessage` | Not dispatched | Not dispatched | Sampling is not exposed as a reShapr server capability. | | Completion | Not dispatched | Not dispatched | An unimplemented surviving modern method returns in-band JSON-RPC `-32601`. | For a method removed by the `2026-07-28` revision, the Gateway returns HTTP `404` with JSON-RPC `-32601`. For another method that remains valid protocol vocabulary but is not implemented, it returns the ordinary JSON-RPC `-32601` response in HTTP `200`. ## Modern request contract | Element | Source of truth | Validation in `1.0.0` | |---|---|---| | Protocol version | `params._meta["io.modelcontextprotocol/protocolVersion"]` | Must name one of the five supported versions when the modern envelope is present. | | `MCP-Protocol-Version` | Mirrors the protocol metadata | If present, it must equal the version in the request body. Non-handshake stateless calls require it to select stateless mode. | | `Mcp-Method` | Mirrors JSON-RPC `method` | If present, it must equal the body method. | | `Mcp-Name` | Mirrors `params.name` or `params.uri` | If present, it must equal the Tool, Prompt, or Resource target in the body. | | `MCP-Session-Id` | Historical session identifier | Do not send it with a stateless request. | A mirror mismatch returns HTTP `400` with JSON-RPC `-32020`. An unsupported version in a modern envelope returns HTTP `400` with JSON-RPC `-32022` and the supported version list in `error.data`. ## Result dialects | Result feature | Legacy dialect | Modern `2026-07-28` dialect | |---|---:|---:| | Existing payload fields such as `tools`, `prompts`, `resources`, `contents`, or `content` | Yes | Yes | | `resultType` discriminator | No | Yes, `complete` for completed results | | `ttlMs` cache hint | No | Yes, when configured for the result | | `cacheScope` hint | No | Yes, when configured for the result | Cache hints apply to compatible list and Resource-read results. They communicate client caching policy; they do not enable response caching in the Gateway. ## Elicitation roles `elicitation/create` in the stateless row is a request produced by the reShapr server for a capable client. It is not a method that an MCP client calls on the reShapr server. Historical clients receive the implementation-specific `URL_ELICITATION_REQUIRED` error and keep the resulting backend credential associated with their MCP session. Stateless clients receive an `input_required` result and must support the returned URL-mode elicitation request. reShapr binds the completed value to the authenticated issuer and subject, so stateless elicitation requires an OAuth-protected Exposition. ## Transport and endpoint scope The matrix applies to MCP over Streamable HTTP on all three reShapr endpoint forms: - the deterministic Exposition identifier endpoint; - the organization and Exposition-name endpoint; - the historical organization, Service, and version endpoint. The historical Service endpoint can advertise the deterministic Exposition endpoint through `X-Reshapr-Preferred-Endpoint`. reShapr `1.0.0` does not expose an MCP WebSocket transport. ## Verification sources - **[McpSchema](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/McpSchema.java)** declares the supported versions, headers, protocol metadata key, and method vocabulary. - **[McpController](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/McpController.java)** owns endpoint routing, request validation, lifecycle handling, and the method dispatcher. - **[LegacyProtocolDialect](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/LegacyProtocolDialect.java)** and **[ModernProtocolDialect](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/mcp/ModernProtocolDialect.java)** define version-specific result shapes. - **[McpProtocolVersionRoutingTest](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/test/java/io/reshapr/proxy/mcp/McpProtocolVersionRoutingTest.java)** verifies session/stateless routing, modern headers, unsupported versions, removed methods, and unimplemented methods. - The **[official MCP specification](https://modelcontextprotocol.io/specification/)** owns the protocol semantics beyond reShapr's implementation boundary. This matrix was last verified with reShapr `1.0.0` on 2026-09-21. Recheck the owner code and tests before changing a version, mode, or method classification. --- ## Prompts As explained in **[Why reShapr?](../overview/why-reshapr.md)**, reShapr can create secure MCP servers in seconds without coding, just by importing your API’s existing artifacts - like **[OpenAPI 3.x](https://www.openapis.org/)** specs, **[GraphQL](https://graphql.org/)** schemas and **[gRPC/Protobuf](https://grpc.io/)** definitions. These artifacts are directly used to produce **[MCP Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)** that are at the core of the Model Context Protocol. Another interesting aspect of MCP is that it may be composed of **[MCP Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources)** and **[MCP Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts)**. While MCP Tools are often enough for practical use of MCP, all these capabilities are likely to be part of a complete and production-ready MCP Server. Contrary to tools, **prompts can’t - and shouldn’t - be directly inferred from an API contract**. They should be designed to provide users with accelerators on how to interact with the model, offer additional instructions and guardrails on how to use tools for a specific use case, or provide additional details on how to orchestrate tool calls. :::tip Watch Prompts in context See [Prompts attached to a controlled GitHub GraphQL service](https://youtu.be/VONwzZ55Jvc) and [Prompts used with an MCP App](https://youtu.be/PVfznWFvKTI). This page remains the canonical specification reference. ::: reShapr provides **an easy way to design and specify your Prompts using a simple YAML description,** called the `Prompts` specification. To provide these Prompts to a reShapr-powered MCP endpoint, write this file and attach it to an existing Service. Here is a minimal example: ```yaml apiVersion: reshapr.io/v1alpha1 kind: Prompts service: name: Petstore version: 1.0.0 prompts: list_pets: title: List the pets description: Browse the catalog to get all the pets result: Get all the pets from the catalog get_pet: title: Get details of a pet with its name description: Get details for a specific pet from the catalog arguments: - name: name description: The name of the pet to retrieve required: true result: |- Get the detailed information on the pet named '${name}'. The MCP tool to call is 'get_pet_by_name', using '${name}' as the tool argument called 'name'. ``` A `Prompts` artifact follows some simple rules: - It always contains an identification section made of `apiVersion` and `kind` properties that **must** have the **`reshapr.io/v1alpha1`** and `Prompts` values respectively, - It **must** be bound to a specific reShapr **[Service](../explanations/services-and-artifacts.md)** using the **`service.name`** and `service.version` properties, whose values **must** match an already discovered Service, - The `prompts` section then defines the prompts: - We have 2 prompts here: `list_pets` and `get_pet` - A prompt **must** always have a `result` which will be returned to the model when called, - A prompt **may** provide optional `title` and `description` to provide more context to the user when choosing an appropriate prompt, - A prompt **may** also provide `arguments` that allow the production of a customized result prompt message, where the user provides their contextual values for each argument. In the case of a prompt using `arguments`, the `result` value **can** be expressed using `${}` expressions that will be replaced by user-provided values. Typically in our example, if the user is looking for a pet named **Rusty**, the prompt will be generated with `Rusty` in the place of the `${name}` placeholder. When the Artifact is attached, reShapr records each `prompts` key as a derived capability. A Configuration Plan exposes the Prompt only when its `includedArtifacts` selection includes that Artifact, or when the selection is absent or empty. See **[Attach and Select reShapr Artifacts](../how-to-guides/select-reshapr-artifacts.md)** for an executable example and its **[validated Prompt Artifact](/examples/context-control/open-meteo-weather-prompt.yaml)**. --- ## Resources As explained in **[Why reShapr?](../overview/why-reshapr.md)**, reShapr can create secure MCP servers in seconds without coding, just by importing your API’s existing artifacts - like **[OpenAPI 3.x](https://www.openapis.org/)** specs, **[GraphQL](https://graphql.org/)** schemas, and **[gRPC/Protobuf](https://grpc.io/)** definitions. These artifacts are directly used to produce **[MCP Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)** that are at the core of the Model Context Protocol. Another interesting aspect of MCP is that it may be composed of **[Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources)**. Resources allow servers to share data that provides context to LLM, such as files, database schemas, or application-specific information. The key difference with tools is intent and usage pattern: - **Resources** are for when **you want to give the model access to existing data to read and reference**. Resources are designed to be **application-driven**, with host applications determining how to incorporate context based on their needs. - **Tools** are for when **you want the model to perform actions** or computations. Tools are designed to be **model-controlled**, meaning that the LLM can discover and invoke tools automatically based on its understanding and the user’s prompts. reShapr provides **an easy way to design and specify your Resources using a simple YAML description,** called the `Resources` specification. If you want to provide such resources to your reShapr-powered MCP endpoint, you’ll need to write this simple file and `attach` it to your existing Service. Let’s see a simple example of such a file: :::note Controllers `0.0.3` can also create this artifact from a Kubernetes **[`Resources` custom resource](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/documentation/resources-cr.md)** using the same `apiVersion` and plural `kind`. ::: ```yaml apiVersion: reshapr.io/v1alpha1 kind: Resources service: name: Open-Meteo APIs version: '1.0' resources: 'file:///project/readme.md': name: README.MD title: Open-Meteo APIs readme description: Documentation on how to use the Open-Meteo APIs mimeType: text/plain icons: - src: file:///assets/icons/md-file-icon.png mimeType: image/png sizes: - "48x48" text: | ## Hello World Welcome to Open-Meteo API! Bla, bla, bla annotations: audience: - user priority: 0.8 ``` A `Resources` artifact follows some simple rules: - It always contains an identification section made of `apiVersion` and `kind` properties that **must** have the **`reshapr.io/v1alpha1`** and `Resources` values respectively, - It **must** be bound to a specific reShapr **[Service](../explanations/services-and-artifacts.md)** using the **`service.name`** and `service.version` properties whose values **must** match an already discovered Service, - The `resources` section then defines the resources: - We have a single resource here: **`file:///project/readme.md`**. Resource identifiers must be valid URIs with a scheme, such as `file:///project/readme.md` or `https://example.com/readme.md`. - A resource **must** always have a `name` that defines its short name, - A resource **may** provide optional `title`, `description`, `mimeType` and `icons` to provide more context to the Agent when choosing an appropriate resource, - A resource **may** also specify its content by using either a `text` or a `blob` property. `text` specifies its content as plain text, `blob` value must be encoded using Base64 - A resource **may** also specify `annotations` that provide hints to clients about how to use or display the resource. More on this in the **[official MCP documentation](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#annotations)**. You can specify as many resources as you want in the same `Resources` artifact file. ## Resource templates As the above form represents the simplest way of doing things, reShapr also supports a more elaborate way of doing things via the concept of `resourceTemplates`. Resource templates represent parametrized resources using URI templates. Imagine you have a list of resources at a specific location (a URL), instead of describing each of them, you can use a template to parameterize the access. Resource Templates can be added to your `Resources` artifact using a `resourceTemplates` element like illustrated below: ```yaml resourceTemplates: 'file:///project/src/{path}?mode=raw': name: Project Source File title: 📁 Project Files description: Access files in the project directory mimeType: application/octet-stream icons: - src: file:///assets/icons/folder-icon.png mimeType: image/png sizes: - "48x48" ``` A `resourceTemplate` follows these rules: - The template identifier **must** contain a parameter delimited with curly braces, here `{path}` - It has the same basic properties as a `resource` except for the `mimeType` property that doesn’t make sense here, as we represent many resources - A template **must not** have `text` or `blob` to define its content. Because it represents a collection of resources, the discovery and fetching of content will be dynamic! To fetch its resource content, a `resourceTemplate` **will reuse [the backend endpoint URL](../explanations/configuration-and-exposition.md) coming from the MCP Server Configuration Plan!** As an example, imagine that you have configured your backend endpoint URL to be **`https://api.acme.com`**. When asking for a resource with `path=resources/doc.md`, your reShapr MCP Server will fetch **`https://api.acme.com/project/src/resources/doc.md?mode=raw`**. Depending on the received content (text or binary), the reShapr endpoint will use the correct encoding to allow your agent or host application to interpret this content. --- ## Tools Output Filtering As explained in **[Why reShapr?](../overview/why-reshapr.md)**, reShapr can create secure MCP servers in seconds without coding, just by importing your API's existing artifacts such as **[OpenAPI 3.x](https://www.openapis.org/)** specs, **[GraphQL](https://graphql.org/)** schemas, and **[gRPC/Protobuf](https://grpc.io/)** definitions. Once these artifacts are imported, the **[Custom Tools](./custom-tools-specification.md)** specification lets you redefine the *input* of a tool to fit a specific use case. `ToolsOutputFilters` is the symmetrical capability on the *output* side: it lets you declaratively control what a tool returns to the model, before the response is wrapped into the JSON-RPC MCP envelope and sent back to the client. This supports two common requirements: - **Response scope.** A GraphQL node with many scalar properties or a REST endpoint returning a deeply nested JSON tree can expose fields that are irrelevant to the task. Filtering at the gateway retains only the required response shape. - **Stable response shape.** Retaining and patching known fields gives the Agent a smaller, more predictable result independent of the underlying API protocol. In reShapr `1.0.0`, a filter-processing error returns the original Tool response. Treat `ToolsOutputFilters` as response shaping, **not as a security or data-loss-prevention boundary**. Prevent access to sensitive fields in the backend contract and authorization layer. reShapr applies filtering universally, regardless of the source protocol (REST, GraphQL, gRPC), because filters operate on the canonical JSON response produced by reShapr's protocol converters. ## A first example Here is a simple `ToolsOutputFilters` artifact that trims the response of a custom GitHub tool down to a few key fields, removes a sensitive field, and adds a value: ```yaml apiVersion: reshapr.io/v1alpha1 kind: ToolsOutputFilters service: name: GitHub GraphQL version: '20250917' filters: get_user_with_latest_followers: jsonRetain: - /data/user/name - /data/user/login - /data/user/bio - /data/user/avatarUrl - /data/user/followers jsonPatches: - op: add path: /data/user/location value: "Worldwide" - op: remove path: /data/user/followers/nodes ``` A `ToolsOutputFilters` artifact follows some simple rules: - It always contains an identification section made of `apiVersion` and `kind` properties that **must** have the **`reshapr.io/v1alpha1`** and `ToolsOutputFilters` values respectively, - It **must** be bound to a specific reShapr **[Service](../explanations/services-and-artifacts.md)** using the **`service.name`** and `service.version` properties whose values **must** match an already discovered Service, - The `filters` section then defines the filters, keyed by tool name: - The key (here `get_user_with_latest_followers`) **must** match an existing tool on the Service, either an imported tool or a **[Custom Tool](./custom-tools-specification.md)** previously attached to that Service, - A filter entry **must** specify at least one of `jsonRetain`, `jsonPatches`, `compact`, or `convertToToon`, - When both `jsonRetain` and `jsonPatches` are present, `jsonRetain` **is always applied first**, as a pre-processing step that narrows the response, before `jsonPatches` are applied in order, - `compact` is applied after `jsonPatches`, and `convertToToon` is applied last. You can specify as many tool filters as you want in the same `ToolsOutputFilters` artifact, as long as each key targets a distinct tool of the bound Service. ## The `jsonRetain` operation `jsonRetain` is reShapr's addition to the JSON Patch vocabulary. Standard JSON Patch (see below) only lets you describe what to *remove*, which is impractical when the response is a large object and you only care about a small subset of it. With `jsonRetain`, you declare the branches you want to *keep*, and everything else is dropped before patches run. - The value of `jsonRetain` **must** be a non-empty list of **[JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)** paths, - Each listed path **must** resolve against the original tool response. Paths that don't resolve are silently ignored (a missing branch isn't an error, it's simply nothing to keep), - The order of paths in `jsonRetain` is not significant: `jsonRetain` describes a *set* of branches to keep, - If `jsonRetain` is omitted, the whole response is passed through to the `jsonPatches` step unchanged. ## The `jsonPatches` operation `jsonPatches` is an ordered sequence of **[JSON Patch](https://jsonpatch.com/)** operations, applied in the listed order to whatever the `jsonRetain` step produced (or to the full response, when `jsonRetain` is omitted). reShapr supports the six standard JSON Patch operations: | Operation | Effect | | ---------- | ----------------------------------------------------------------------------------------------- | | `add` | Adds a value at the given path. Creates the field if it doesn't exist; inserts into arrays. | | `replace` | Replaces the value at the given path. The path **must** already exist. | | `remove` | Removes the value at the given path. | | `copy` | Copies the value from `from` to `path`. | | `move` | Moves the value from `from` to `path` (equivalent to `copy` + `remove` on the source). | | `test` | Asserts that the value at `path` equals `value`. If the test fails, the patch sequence stops. | - The value of `jsonPatches` **must** be a non-empty list of objects, each carrying an `op` property whose value **must** be one of the six operations above, - `path` **must** be a JSON Pointer that resolves against the working document (the response after `jsonRetain` has been applied), - `op: copy` and `op: move` **must** additionally specify a `from` JSON Pointer, - `op: add`, `replace`, and `test` **must** specify a `value`, - The order of the list is significant: each operation sees the document as modified by the operations that came before it. For the precise semantics of each operation, refer to **[RFC 6902: JavaScript Object Notation (JSON) Patch](https://datatracker.ietf.org/doc/html/rfc6902)**. ## The `compact` operation Set `compact: true` to recursively remove sparse values from the filtered JSON response: - `null` values; - empty strings; - empty arrays; - empty objects. Compaction also removes a parent array or object when pruning its children leaves it empty. It runs after `jsonRetain` and `jsonPatches`, and before `convertToToon`. Omit `compact` or set it to `false` when an empty value carries domain meaning that the client must preserve. ## The `convertToToon` operation `convertToToon` converts the final filtered JSON output into **[Toon format](https://toonformat.dev/)**, a compact representation intended for LLM consumption. - The value of `convertToToon` **must** be `true`, - It is applied **last**, after `jsonRetain`, `jsonPatches`, and `compact` have run, - It can be used **alone** — without any `jsonRetain` or `jsonPatches` — and it will compact the full raw tool response as-is, - It works **regardless of the backend protocol**: REST, GraphQL, and gRPC tool responses are all converted to canonical JSON before filters run, so `convertToToon` applies uniformly across all three. This makes `convertToToon: true` the simplest possible `ToolsOutputFilters` entry: a single key that converts any tool response without requiring you to know its shape upfront. ```yaml apiVersion: reshapr.io/v1alpha1 kind: ToolsOutputFilters service: name: GitHub GraphQL version: '20250917' filters: get_user_with_latest_followers: convertToToon: true ``` You can also combine it with `jsonRetain` and `jsonPatches` to both shape and compact the response: ```yaml apiVersion: reshapr.io/v1alpha1 kind: ToolsOutputFilters service: name: GitHub GraphQL version: '20250917' filters: get_user_with_latest_followers: jsonRetain: - /data/user/name - /data/user/login - /data/user/bio - /data/user/avatarUrl - /data/user/followers jsonPatches: - op: add path: /data/user/location value: "Worldwide" - op: remove path: /data/user/followers/nodes convertToToon: true ``` ## Naming and configuration scope Like other complementary artifacts, a `ToolsOutputFilters` file is attached to a Service. A **[Configuration Plan](../explanations/configuration-and-exposition.md)** controls whether that attached artifact contributes to an Exposition through its `includedArtifacts` selection. - When `includedArtifacts` is absent or empty, all complementary artifacts attached to the Service apply, including every attached `ToolsOutputFilters` artifact. - When `includedArtifacts` contains artifact names, only those attached artifacts apply to the Configuration Plan. This selection lets two Configuration Plans expose the same Service with different output filters. For example, `partner-plan` can set `includedArtifacts` to `partner-output-filters.yaml`, while `internal-plan` selects `internal-output-filters.yaml`. Each Exposition then uses the filters selected by its Configuration Plan. ## Where filters fit in the request lifecycle For a given MCP tool call, reShapr applies transformations in this order: 1. The incoming MCP tool call is validated against the tool's input schema (the imported one, or the one defined by a **[Custom Tool](./custom-tools-specification.md)**). 2. The call is converted to a protocol-specific request (REST, GraphQL, gRPC) and dispatched to the backend. 3. The backend response is converted back into a canonical JSON response by reShapr's converters. 4. **If a `ToolsOutputFilters` artifact is attached to the Service and declares a filter for this tool, the filter is applied here: `jsonRetain` first, then `jsonPatches`, then `compact`, and finally `convertToToon` if set.** 5. The filtered response is wrapped into the JSON-RPC MCP envelope and returned to the client. Because filtering happens after the converter step and before the MCP envelope, the same `ToolsOutputFilters` artifact applies uniformly whether the underlying tool is backed by REST, GraphQL, or gRPC. To compare an unfiltered and filtered response with identical Tool arguments, follow **[Context Control in Practice](../tutorials/context-control-in-practice.md)**. --- ## Demos # Demos Use these short videos to see a reShapr workflow before following its written procedure. The written alternative is the maintained source for commands, prerequisites, and current product behavior. [View the reShapr channel](https://www.youtube.com/@reShaprio/videos) ## Start and expose an API Install reShapr locally, start its components, and expose an OpenAPI service as an MCP endpoint.
[https://youtu.be/bmSPkisbqJo](https://youtu.be/bmSPkisbqJo) Install the reShapr CLI Published April 9, 2026 · 11 seconds · Version not stated Prerequisite: Node.js 20 or later. Result: the reshapr command is available locally. Historical recording · Follow the current written procedure. [Written alternative](/docs/tutorials/try-reshapr-online#login-workflow) · [Watch video](https://youtu.be/bmSPkisbqJo)
[https://youtu.be/ECZAiXbSwDc](https://youtu.be/ECZAiXbSwDc) Run reShapr locally with Docker Published April 9, 2026 · 11 seconds · Version not stated Prerequisite: the reShapr CLI and Docker or Podman. Result: the local reShapr components are running. Historical recording · Follow the current written procedure. [Written alternative](/docs/how-to-guides/docker-compose) · [Watch video](https://youtu.be/ECZAiXbSwDc)
[https://youtu.be/EmBNZfUceTI](https://youtu.be/EmBNZfUceTI) Import Open-Meteo and expose it through MCP Published April 9, 2026 · 43 seconds · Version not stated Prerequisite: a running reShapr environment and the CLI. Result: an OpenAPI service is available through an MCP endpoint. Historical recording · Follow the current written procedure. [Written alternative](/docs/tutorials/getting-started) · [Watch video](https://youtu.be/EmBNZfUceTI)
## Apply Context Control Reduce and reshape a GitHub GraphQL API into focused MCP capabilities.
[https://youtu.be/OjsSAt0JdOY](https://youtu.be/OjsSAt0JdOY) Apply Context Control to GitHub GraphQL Published May 5, 2026 · 2 minutes 2 seconds · Version not stated Prerequisite: a GitHub GraphQL service imported into reShapr. Result: the exposed MCP surface contains only task-relevant capabilities. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/tutorials/context-control-in-practice) · [Watch video](https://youtu.be/OjsSAt0JdOY)
[https://youtu.be/VONwzZ55Jvc](https://youtu.be/VONwzZ55Jvc) Add Prompts to a controlled GraphQL surface Published May 12, 2026 · 1 minute 4 seconds · Version not stated Prerequisite: an exposed GitHub GraphQL service. Result: an MCP Prompt guides a client through the focused workflow. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/references/prompts-specification) · [Watch video](https://youtu.be/VONwzZ55Jvc)
[https://youtu.be/5ewU51_oM_8](https://youtu.be/5ewU51_oM_8) Use Context Control with an MCP App Published May 13, 2026 · 45 seconds · Version not stated Prerequisite: a GitHub GraphQL MCP endpoint and a compatible MCP App. Result: the app consumes the focused capabilities exposed by reShapr. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/tutorials/context-control-in-practice) · [Watch video](https://youtu.be/5ewU51_oM_8)
[https://youtu.be/PVfznWFvKTI](https://youtu.be/PVfznWFvKTI) Combine an MCP App with Prompts Published May 27, 2026 · 2 minutes · Version not stated Prerequisite: an exposed GitHub GraphQL service with a Prompt Artifact. Result: the MCP App uses a Prompt to drive the focused workflow. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/references/prompts-specification) · [Watch video](https://youtu.be/PVfznWFvKTI)
## Secure MCP and backend access Keep MCP client authentication separate from the credentials used by the proxy to call backend APIs.
[https://youtu.be/y38__Uj5gWo](https://youtu.be/y38__Uj5gWo) Elicit a token for GitHub GraphQL Published June 9, 2026 · 1 minute 47 seconds · Version not stated Prerequisite: a GitHub token and an MCP client that supports elicitation. Result: the proxy receives a credential for authenticated backend calls. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/how-to-guides/security/backend-auth-and-elicitation) · [Watch video](https://youtu.be/y38__Uj5gWo)
[https://youtu.be/dC41-ieQIqk](https://youtu.be/dC41-ieQIqk) Use OAuth 2.0 URL elicitation with Keycloak Published June 25, 2026 · 1 minute 57 seconds · Version not stated Prerequisite: Keycloak, Open-Meteo, and an MCP client with URL elicitation support. Result: the proxy obtains OAuth credentials for backend access. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/how-to-guides/security/backend-auth-and-elicitation) · [Watch video](https://youtu.be/dC41-ieQIqk)
[https://youtu.be/_DTf34OaLr0](https://youtu.be/_DTf34OaLr0) Separate endpoint and backend OAuth Published August 5, 2026 · 1 minute 30 seconds · MCP 2026-07-28 Prerequisite: OAuth providers for the MCP endpoint and backend API. Result: client authentication and backend authorization use separate flows. Reference demo · Replay not verified against current baseline reShapr 1.0.0. [Written alternative](/docs/how-to-guides/security/backend-auth-and-elicitation) · [Watch video](https://youtu.be/_DTf34OaLr0)
## Follow MCP protocol evolution See how a reShapr MCP endpoint negotiates the current stateless protocol alongside historical session-based versions.
[https://youtu.be/iqk3lcuASD8](https://youtu.be/iqk3lcuASD8) Negotiate MCP 2026-07-28 Published August 5, 2026 · 52 seconds · MCP 2026-07-28 Prerequisite: a reShapr MCP endpoint and a compatible MCP client. Result: the client negotiates the stateless protocol mode. Supported by reShapr 1.0.0 · Video replay not independently verified. [Written alternative](/docs/explanations/mcp-compatibility) · [Watch video](https://youtu.be/iqk3lcuASD8)
:::info Catalog scope This page covers all 11 public videos visible on the reShapr channel on September 7, 2026. Check the channel for videos published after that date. ::: --- ## Assign and Monitor Organization Quotas Use this guide to limit how many Expositions, Gateway Groups, and Gateway registrations an organization can create. You can manage the same quota records interactively in the Web UI, from an administrative script with the CLI, or through the administration API. Quotas restrict resource counts. They do not rate-limit MCP requests or reserve CPU, memory, network, or database capacity. See **[Multi-tenancy and Administrative Governance](../../explanations/multi-tenancy-administrative-governance.md#quotas-govern-resource-counts)** for the tenancy and enforcement model. ## Prerequisites You need: - a reShapr `1.0.0` control plane and the name of an existing organization; - the deployment-wide administration API key; - reShapr CLI `1.0.0` for the CLI path; - a deployed Web UI configured with its server-side administration API key for the Web UI path; - `curl` and `jq` for the API path; - a normal user session with membership in the target organization to perform the tenant-side verification. Use **[Manage Organizations, Owners, and Memberships](./organizations-and-memberships.md)** first when the target organization or its user access does not exist yet. Set the non-sensitive values used in the examples: ```bash export RESHAPR_SERVER='https://app.reshapr.example.com' export RESHAPR_ORGANIZATION='production' ``` The supported metrics are: | Metric | Limited resource | |---|---| | `exposition.count` | Expositions | | `gateway-group.count` | Gateway Groups | | `gateway.count` | Active Gateway registrations | An enabled quota with a limit of `0` prevents new consumption of that metric. A disabled quota remains visible but is not enforced. ## Choose an administration path | Path | Use it when | |---|---| | Web UI | An administrator needs to inspect current usage and adjust limits interactively | | CLI | A controlled script or one-off terminal operation needs to assign enabled flags and limits | | Administration API | An administration service needs to read quotas or control how remaining capacity changes | All three paths require platform-level administration authority. Organization ownership or membership alone does not grant access to these operations. ## Assign quotas with the Web UI The Web UI keeps the administration API key on its server side. The browser session must belong to the built-in `reshapr` organization for the **Admin** navigation to be available. 1. Sign in to the Web UI with a platform administrator account. 2. Open **Admin**, then **Quotas**. 3. Search for and select the target organization. 4. Enable each metric that should be enforced. 5. Enter a non-negative integer limit for each enabled metric. 6. Review the usage gauge and the projected remaining capacity. 7. Select **Save Changes**. The Web UI preserves the represented consumption when a limit changes. For example, if a quota has a limit of `10` and `3` remaining, increasing the limit to `15` produces `8` remaining. ## Assign quotas with the CLI Retrieve the administration API key from your secret manager and expose it only to the administrative process: ```bash set +x export RESHAPR_ADMIN_API_KEY='' ``` Assign all three metrics and request structured output: ```bash ASSIGNED_QUOTAS="$( reshapr admin --server "${RESHAPR_SERVER}" \ quota assign "${RESHAPR_ORGANIZATION}" \ --quotas '[ {"metric":"exposition.count","enabled":true,"limit":10}, {"metric":"gateway-group.count","enabled":true,"limit":3}, {"metric":"gateway.count","enabled":true,"limit":3} ]' \ --output json )" ``` Confirm that the response contains the requested limits: ```bash jq -e --arg organization "${RESHAPR_ORGANIZATION}" ' def quota($metric; $limit): any(.[]; .organizationId == $organization and .metric == $metric and .enabled == true and .limit == $limit ); quota("exposition.count"; 10) and quota("gateway-group.count"; 3) and quota("gateway.count"; 3) ' <<<"${ASSIGNED_QUOTAS}" >/dev/null ``` Unset the key when the administration step is complete: ```bash unset RESHAPR_ADMIN_API_KEY ``` The CLI command uses the standard assignment API. New quota records start with `remaining` equal to `limit`. Updating an existing record changes `enabled` and `limit`, but increasing its limit does not increase its existing `remaining` value. Use the Web UI or the controlled API procedure below when increasing a limit must make the additional capacity immediately available. ## Manage quotas with the administration API Avoid placing the administration key directly in a `curl` command argument. Store the header in a mode-`0600` temporary configuration file: ```bash set +x export RESHAPR_ADMIN_API_KEY='' export RESHAPR_CURL_CONFIG="$(mktemp)" chmod 600 "${RESHAPR_CURL_CONFIG}" printf 'header = "x-reshapr-api-key: %s"\n' "${RESHAPR_ADMIN_API_KEY}" \ >"${RESHAPR_CURL_CONFIG}" unset RESHAPR_ADMIN_API_KEY trap 'rm -f "${RESHAPR_CURL_CONFIG}"' EXIT ``` Read the organization's current quota state: ```bash CURRENT_QUOTAS="$( curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ "${RESHAPR_SERVER}/api/admin/quotas/organization/${RESHAPR_ORGANIZATION}" )" jq . <<<"${CURRENT_QUOTAS}" ``` The response is an array containing `organizationId`, `metric`, `enabled`, `limit`, and `remaining` for each assigned metric. ### Apply a standard assignment The standard API has the same update behavior as `reshapr admin quota assign`. Omitted metrics remain unchanged: ```bash QUOTA_REQUEST='[ {"metric":"exposition.count","enabled":true,"limit":10}, {"metric":"gateway-group.count","enabled":true,"limit":3}, {"metric":"gateway.count","enabled":true,"limit":3} ]' curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ --request POST \ --header 'Content-Type: application/json' \ --data "${QUOTA_REQUEST}" \ "${RESHAPR_SERVER}/api/admin/quotas/organization/${RESHAPR_ORGANIZATION}" \ | jq . ``` Set `enabled` to `false` for an assigned metric to stop enforcing it. Disabling a quota does not delete resources or reset its stored counters. ### Increase a limit while preserving consumption The `/force` operation accepts an explicit `remaining` value. Calculate it from the current quota rather than guessing or resetting it to the new limit. This example increases `exposition.count` to `20` while preserving represented consumption: ```bash export QUOTA_METRIC='exposition.count' export NEW_LIMIT='20' FORCED_REQUEST="$( jq -ce \ --arg metric "${QUOTA_METRIC}" \ --argjson newLimit "${NEW_LIMIT}" ' [.[] | select(.metric == $metric)] as $matches | if ($matches | length) != 1 then error("assigned quota not found") else $matches[0] | (.limit - .remaining) as $used | [{ metric, enabled: true, limit: $newLimit, remaining: ([$newLimit - $used, 0] | max) }] end ' <<<"${CURRENT_QUOTAS}" )" curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ --request POST \ --header 'Content-Type: application/json' \ --data "${FORCED_REQUEST}" \ "${RESHAPR_SERVER}/api/admin/quotas/organization/${RESHAPR_ORGANIZATION}/force" \ | jq . ``` The force endpoint directly sets `remaining`. Restrict its use to administration code that first reads the current state and preserves the intended consumption. The Web UI performs this calculation when saving changes. ## Verify from the organization context Authenticate a normal user who is a member of the target organization, then select that organization: ```bash reshapr switch-org "${RESHAPR_ORGANIZATION}" ORGANIZATION_QUOTAS="$(reshapr quotas --output json)" ``` Check that every supported metric is enabled and has non-negative remaining capacity no greater than its limit: ```bash jq -e ' length == 3 and all(.[]; .metric == "exposition.count" or .metric == "gateway-group.count" or .metric == "gateway.count" ) and all(.[]; .enabled == true and .remaining >= 0 and .remaining <= .limit ) ' <<<"${ORGANIZATION_QUOTAS}" >/dev/null ``` ## Result The target organization has explicit limits for Expositions, Gateway Groups, and Gateway registrations. Administrators can read and change them, while organization members can inspect their current limits and remaining capacity with `reshapr quotas` or the Web UI dashboard. When an enabled quota reaches zero remaining, the corresponding create or first-registration operation is rejected. Deleting an Exposition or Gateway Group releases its unit; Gateway shutdown or stale-registration cleanup releases a Gateway unit. ## Limits - The administration API key is global to the deployment, not scoped to the target organization. - Quotas count resources; they do not provide request throttling, infrastructure reservations, or availability guarantees. - Assigning or lowering a quota does not delete existing resources. - The standard CLI and API update does not add to `remaining` when an existing limit is raised. - The force API trusts the supplied `remaining` value and can make accounting inconsistent when callers do not preserve current consumption. - Omitting a metric from an assignment leaves its existing quota unchanged. ## Next step Use **[Automate reShapr with the CLI in CI/CD](../automate-with-cli-in-cicd.md)** to place quota checks around resource automation. Review **[Product Interfaces](../../references/interfaces.md)** to choose the canonical API or CLI reference for a deeper integration. The release-tagged [administration API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-admin-ctrl-openapi-v0.1.yaml), [administrative CLI guide](https://github.com/reshaprio/reshapr/blob/1.0.0/cli/ADMIN_CLI.md), and [Web UI implementation](https://github.com/reshaprio/reshapr/tree/1.0.0/web-ui) remain the canonical interface references. --- ## Manage Organizations, Owners, and Memberships Use this guide to create an organization, assign its owner, and grant existing users access. The Web UI supports the common interactive workflow, while the CLI and administration API cover repeatable operations and membership changes for existing users. An owner and a member serve different purposes. Ownership is administrative metadata for one user. Membership controls whether a user can select the organization for normal product API calls. Assigning an owner also adds that user as a member, but replacing memberships later can remove that access without changing the recorded owner. For federated users, first configure **[control-plane OIDC authentication](../configure-control-plane-oidc.md)**. Its default-organization rules can attach a first-time user to an organization created with this guide; later membership changes still use the procedures below. ## Prerequisites You need: - a reShapr `1.0.0` control plane; - the deployment-wide administration API key; - at least one existing user to assign as owner or member; - reShapr CLI `1.0.0` for the CLI path; - a deployed Web UI configured with its server-side administration API key for the Web UI path; - `curl` and `jq` for the API path; - access to a target user's normal login flow for the final membership check. Set the non-sensitive values used in the examples: ```bash export RESHAPR_SERVER='https://app.reshapr.example.com' export RESHAPR_ORGANIZATION='production' export OWNER_USERNAME='platform-owner' export MEMBER_USERNAME='release-engineer' ``` Organization names in `1.0.0` contain only letters, digits, and underscores, with a maximum length of 100 characters. ## Choose an administration path | Path | Supported workflow in `1.0.0` | |---|---| | Web UI | Create organizations, assign or replace owners, create local users, and assign memberships during local-user creation | | CLI | Create organizations with or without an owner and replace all memberships of an existing user | | Administration API | Create organizations, assign or replace owners, and replace all memberships of an existing user | The Web UI does not provide a membership editor for an existing user in `1.0.0`. Use the CLI or API for that operation. ## Create and delegate with the Web UI The Web UI keeps the administration API key on its server side. The browser session must belong to the built-in `reshapr` organization for the **Admin** navigation to be available. 1. Sign in to the Web UI with a platform administrator account. 2. Open **Admin**, then **Organizations**. 3. Select **New Organization**. 4. Enter the name and optional description and icon URL, then select **Create Organization**. 5. Open the new organization's actions menu and select **Assign owner**. 6. Select an existing user and confirm the assignment. Assigning the owner also adds that organization to the user's memberships. If the user had no default organization, it becomes their default. To create a local user and assign memberships in the same workflow: 1. Open the **Users** tab and select **New User**. 2. Enter the username, email, optional profile fields, and a password when the control plane handles authentication directly. 3. In the second step, select every organization the new user should join. 4. Select **Assign Memberships**. The local-user creation action is not displayed when the Web UI uses another authentication mode. Memberships for an already existing local or federated user must be changed with the CLI or API. ## Create an organization with the CLI Retrieve the administration API key from your secret manager and expose it only to the administrative process: ```bash set +x export RESHAPR_ADMIN_API_KEY='' ``` Create an organization and assign an existing owner in one operation: ```bash ORGANIZATION="$( reshapr admin --server "${RESHAPR_SERVER}" \ organization create "${RESHAPR_ORGANIZATION}" \ --description 'Production services' \ --owner "${OWNER_USERNAME}" \ --output json )" ``` Confirm the returned organization: ```bash jq -e --arg name "${RESHAPR_ORGANIZATION}" \ '.name == $name' <<<"${ORGANIZATION}" >/dev/null ``` Omit `--owner` to create an unowned organization. The CLI does not expose a command for assigning or replacing the owner of an existing organization in `1.0.0`; use the Web UI or administration API for that operation. ## Replace memberships with the CLI `membership set` replaces the user's complete membership list. Build the desired final state from your authoritative identity or access configuration; do not pass only the organization being added. For example, to preserve an existing `development` membership and add `production`: ```bash export DESIRED_MEMBERSHIPS='["development","production"]' ASSIGNED_MEMBERSHIPS="$( reshapr admin --server "${RESHAPR_SERVER}" \ membership set "${MEMBER_USERNAME}" \ --organizations "${DESIRED_MEMBERSHIPS}" \ --output json )" jq -e \ --arg organization "${RESHAPR_ORGANIZATION}" \ 'index($organization) != null' \ <<<"${ASSIGNED_MEMBERSHIPS}" >/dev/null ``` The response echoes the requested organization names. It does not prove that every submitted name existed and was persisted. Complete the effective-access check below before considering the change successful. Unset the administration key when the operation is complete: ```bash unset RESHAPR_ADMIN_API_KEY ``` ## Manage organizations with the administration API Avoid placing the administration key directly in a `curl` command argument. Store the header in a mode-`0600` temporary configuration file: ```bash set +x export RESHAPR_ADMIN_API_KEY='' export RESHAPR_CURL_CONFIG="$(mktemp)" chmod 600 "${RESHAPR_CURL_CONFIG}" printf 'header = "x-reshapr-api-key: %s"\n' "${RESHAPR_ADMIN_API_KEY}" \ >"${RESHAPR_CURL_CONFIG}" unset RESHAPR_ADMIN_API_KEY trap 'rm -f "${RESHAPR_CURL_CONFIG}"' EXIT ``` ### Create an unowned organization ```bash ORGANIZATION_REQUEST="$( jq -n \ --arg name "${RESHAPR_ORGANIZATION}" \ --arg description 'Production services' \ '{name: $name, description: $description}' )" curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ --request POST \ --header 'Content-Type: application/json' \ --data "${ORGANIZATION_REQUEST}" \ "${RESHAPR_SERVER}/api/admin/organizations" \ | jq . ``` The operation returns `409 Conflict` when the organization name already exists. ### Assign or replace the owner The owner API expects the username as a JSON string: ```bash OWNER_REQUEST="$(jq -Rn --arg username "${OWNER_USERNAME}" '$username')" curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ --request PUT \ --header 'Content-Type: application/json' \ --data "${OWNER_REQUEST}" \ "${RESHAPR_SERVER}/api/admin/organizations/${RESHAPR_ORGANIZATION}/owner" \ | jq -e \ --arg organization "${RESHAPR_ORGANIZATION}" \ --arg owner "${OWNER_USERNAME}" \ '.name == $organization and .ownerUsername == $owner' ``` This operation also adds the organization to the new owner's memberships. It does not remove the previous owner's membership. ### Replace an existing user's memberships Send the complete desired list, not an incremental addition: ```bash export DESIRED_MEMBERSHIPS='["development","production"]' curl --fail --silent --show-error \ --config "${RESHAPR_CURL_CONFIG}" \ --request PUT \ --header 'Content-Type: application/json' \ --data "${DESIRED_MEMBERSHIPS}" \ "${RESHAPR_SERVER}/api/admin/users/${MEMBER_USERNAME}/memberships" \ | jq -e \ --arg organization "${RESHAPR_ORGANIZATION}" \ 'index($organization) != null' ``` An empty array removes all memberships from that user. In `1.0.0`, this operation does not reconcile organization ownership, update the user's default organization, or expose a dedicated read endpoint for the user's complete membership list. Preserve the intended list in an authoritative source outside this write-only workflow. ## Verify effective membership The final check must run as the target user, not with the administration API key. Authenticate through that user's normal flow, then select the organization: ```bash reshapr switch-org "${RESHAPR_ORGANIZATION}" reshapr info ``` `switch-org` exits non-zero with `403` when the authenticated user is not a member. A successful switch produces a new user token scoped to the target organization; `reshapr info` confirms the active context. Repeat this check for the owner and every member whose access is required. It catches unknown organization names that the `1.0.0` membership replacement endpoint can otherwise omit from persistence while echoing the submitted list. ## Result The organization exists with an explicit owner, and each intended user can select it for normal product operations. The complete membership state remains controlled outside reShapr so future replacement operations preserve required access. ## Limits - The administration API key is global to the deployment, not scoped to one organization. - Ownership does not provide physical infrastructure isolation or replace membership checks. - `membership set` and its API endpoint replace all memberships; they are not additive operations. - The Web UI `1.0.0` assigns memberships only during local-user creation and cannot edit an existing user's memberships. - The `1.0.0` administration API does not expose a dedicated operation for reading a user's complete memberships. - Membership replacement does not update the user's default organization. - Membership replacement can remove an owner's access without clearing the organization's owner field. - Creating or assigning an organization does not provision compute, storage, network policy, or a dedicated database schema. - Organization deletion is destructive and cascades across owned resources; follow the offboarding guidance before using it. ## Next step Use **[Assign and Monitor Organization Quotas](./organization-quotas.md)** to bound the resources the new organization can create. Review **[Multi-tenancy and Administrative Governance](../../explanations/multi-tenancy-administrative-governance.md)** for isolation, identity, and offboarding boundaries. The release-tagged [administration API contract](https://github.com/reshaprio/reshapr/blob/1.0.0/reshapr-admin-ctrl-openapi-v0.1.yaml), [administrative CLI guide](https://github.com/reshaprio/reshapr/blob/1.0.0/cli/ADMIN_CLI.md), and [Web UI implementation](https://github.com/reshaprio/reshapr/tree/1.0.0/web-ui) remain the canonical interface references. --- ## Observe the reShapr Proxy Use this guide to export proxy traces, metrics, and logs with [OpenTelemetry](https://opentelemetry.io/) to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). You will also verify trace propagation to backends, route audit log records independently, and expose the proxy metrics scrape target. This procedure configures the proxy telemetry pipeline. Enabling audit for a particular MCP endpoint is a separate Configuration Plan decision. ## Prerequisites You need: - a reShapr proxy `1.0.0` deployed with the proxy chart `0.0.14`; - an OpenTelemetry Collector endpoint reachable from the proxy namespace; - a telemetry backend where you can search exported logs and traces; - an existing MCP endpoint and an instrumented backend for end-to-end trace verification; - `kubectl`, Helm, `curl`, and `jq`; - Prometheus Operator CRDs when enabling the ServiceMonitor; verify them with `kubectl get crd servicemonitors.monitoring.coreos.com`. This guide uses OTLP over HTTP on port `4318`. Adapt the endpoint and protocol together when your collector uses gRPC or requires authentication. Set the deployment and resource inputs: ```bash export PROXY_NAMESPACE='reshapr-proxies' export PROXY_RELEASE='reshapr-proxy' export OTEL_ENDPOINT='http://otel-collector.observability.svc.cluster.local:4318' ``` ## Configure proxy telemetry Create a focused Helm values file: ```yaml title="values/proxy-observability.yaml" extraEnv: - name: QUARKUS_OTEL_SDK_DISABLED value: "false" - name: QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-collector.observability.svc.cluster.local:4318" - name: QUARKUS_OTEL_EXPORTER_OTLP_PROTOCOL value: "http/protobuf" serviceMonitor: enabled: true additionalLabels: prometheus: kube-prometheus interval: 30s scrapeTimeout: 10s ``` The proxy chart disables the OpenTelemetry SDK by default to avoid connection errors when no collector is available. The override enables the SDK; the proxy application enables traces, metrics, and logs in its production profile. Apply the values to the existing release: ```bash helm upgrade "${PROXY_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-proxy \ --version 0.0.14 \ --namespace "${PROXY_NAMESPACE}" \ --reuse-values \ --values values/proxy-observability.yaml kubectl rollout status deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --timeout 5m ``` Confirm the rendered OTLP settings without displaying secret headers: ```bash kubectl get deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --output json \ | jq '.spec.template.spec.containers[0].env | map(select(.name | startswith("QUARKUS_OTEL_"))) | map({name, value})' ``` The output must show `QUARKUS_OTEL_SDK_DISABLED=false`, the collector endpoint, and `http/protobuf`. Inspect proxy logs if the collector cannot be reached: ```bash kubectl logs deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --tail 100 ``` ## Follow a distributed trace When an MCP caller supplies a valid W3C trace context, the Gateway continues that trace and contributes spans for MCP request handling, Tool execution, and backend client calls. For HTTP backends, it injects the current [W3C Trace Context](https://www.w3.org/TR/trace-context/) and [W3C Baggage](https://www.w3.org/TR/baggage/) into the outgoing request. ```mermaid sequenceDiagram participant Client as MCP client participant Proxy as reShapr proxy participant Backend as Backend API participant Collector as OTEL Collector Client->>Proxy: MCP request + traceparent Note over Proxy: MCP and Tool spans Proxy->>Backend: API request + traceparent + baggage Note over Proxy,Backend: Proxy backend-client span Proxy-->>Collector: Proxy spans Backend-->>Collector: Backend spans, when instrumented ``` The backend must be instrumented and configured to extract the propagated context before it can contribute its own spans. Collector connectivity alone does not instrument the backend. Send a known read-only `tools/call` from a client that injects `traceparent`, then search your tracing backend for that trace ID. A complete trace should connect the inbound proxy request to its Tool and backend-client spans; an instrumented backend should continue the same trace. Use **[Test an MCP endpoint](../test-mcp-endpoint.md)** for the request shape. These spans let platform engineers separate proxy processing time from the backend call duration and locate failures at the relevant boundary. Sampling still determines whether all participating spans are retained. ## Route audit logs separately Audit events use the proxy's OpenTelemetry Logs pipeline. Every audit record carries `log.type=audit`, allowing the Collector to route it independently from regular application logs. The proxy does not provide a separate audit database or SIEM integration. The following bounded Collector example sends non-audit logs to an observability backend and audit logs to a dedicated sink. Adapt exporter endpoints, authentication, TLS, batching, and component availability to your Collector distribution: ```yaml title="otel-collector-config.yaml" receivers: otlp: protocols: grpc: {} http: {} processors: filter/drop_audit: error_mode: ignore logs: log_record: - 'attributes["log.type"] == "audit"' filter/keep_only_audit: error_mode: ignore logs: log_record: - 'attributes["log.type"] != "audit"' batch: {} exporters: otlphttp/observability: endpoint: https:// otlphttp/audit_sink: endpoint: https:// service: pipelines: logs/application: receivers: [otlp] processors: [filter/drop_audit, batch] exporters: [otlphttp/observability] logs/audit: receivers: [otlp] processors: [filter/keep_only_audit, batch] exporters: [otlphttp/audit_sink] ``` Collector filter conditions remove matching records. Consequently, `filter/drop_audit` excludes audit records from the application sink, while `filter/keep_only_audit` excludes every non-audit record from the audit sink. To retain audit records in both systems, omit `filter/drop_audit` from the application pipeline. Review the official [Collector configuration documentation](https://opentelemetry.io/docs/collector/configuration/) and the [filter processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor) before applying this pattern. Treat the audit sink as security-sensitive and apply its own access, integrity, retention, and deletion controls. This routing configuration receives audit records only for Configuration Plans where audit is enabled. Use **[Audit MCP Endpoint Calls](../audit-mcp-endpoint.md)** to activate and verify that endpoint policy. ## Verify proxy metrics Confirm that the chart created a ServiceMonitor selecting the proxy service. When the Prometheus Operator CRDs are not installed, keep `serviceMonitor.enabled=false`; the Helm release cannot create that resource, but `/q/metrics` remains available for direct verification. ```bash kubectl get servicemonitor reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --output json \ | jq '{selector: .spec.selector, endpoints: .spec.endpoints}' ``` The endpoint must use the `http` port, `/q/metrics` path, `30s` interval, and `10s` timeout. Confirm in Prometheus that the corresponding target is up. To inspect the metrics without exposing the management endpoint through ingress, start a local port-forward in another terminal: ```bash kubectl port-forward \ --namespace "${PROXY_NAMESPACE}" \ service/reshapr-proxy 7777:7777 ``` Then query the metrics endpoint: ```bash curl --fail --silent http://localhost:7777/q/metrics | head ``` The ServiceMonitor only configures discovery and scraping. Your Prometheus installation owns target selection, retention, recording rules, alerts, and dashboards. ## Roll back To stop exporting telemetry, remove the observability overrides from the release values or restore `QUARKUS_OTEL_SDK_DISABLED=true`, then upgrade the release again. Disable `serviceMonitor.enabled` separately if Prometheus must stop scraping the proxy. ## Result The proxy exports traces, metrics, and logs to your Collector, continues distributed traces across HTTP backend calls, and exposes a Prometheus scrape target. Audit records can follow a dedicated Collector pipeline based on `log.type=audit`. ## Limits - Release `1.0.0` proves OpenTelemetry behavior for the proxy. It does not establish equivalent coverage for the control plane, Web UI, operator, or admission controller. - End-to-end traces require callers and backends to propagate compatible trace context and export their own spans. - Telemetry export depends on the OpenTelemetry SDK, Collector connectivity, configured pipelines, sampling, and backend retention. - Audit records appear only for Configuration Plans where audit is enabled. - The proxy ServiceMonitor does not install Prometheus Operator or create alerts and dashboards. - Telemetry can contain organization, service, user, source-address, and target metadata. Apply access controls and retention appropriate to that data. ## Next step Use **[Audit MCP Endpoint Calls](../audit-mcp-endpoint.md)** to enable audit on a Configuration Plan. Use **[Troubleshoot an Exposition or Proxy](./troubleshoot.md)** to choose the relevant signal for a failed request. The release-tagged [proxy telemetry configuration](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/resources/application.properties) and [proxy chart values](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/proxy/values.yaml) remain the canonical configuration references. --- ## Rotate the Database Encryption Key Use this runbook to introduce a new AES-256-GCM key, activate it safely across multiple control-plane replicas, and re-encrypt stored Secret and Configuration Plan values. Key generation, distribution, activation, invocation, retirement, and scheduling are operator-managed in reShapr `1.0.0`. The procedure uses two workload rollouts. The first gives every replica the new key while the old key remains active. The second changes the active key only after every replica can decrypt values written with either key. ## Prerequisites You need: - reShapr control plane `1.0.0` deployed with Helm chart `0.0.14`; - an externally managed PostgreSQL backup with a tested restore procedure; - an external Kubernetes Secret referenced by `encryptionKey.existingSecret`; - the current active key and every older key still needed by live data or retained backups; - the reShapr `1.0.0` CLI configured with `RESHAPR_ADMIN_API_KEY`; - permission to update the Secret and roll out every control-plane replica; - one Exposition that uses an encrypted API key or backend Secret for the final check; - Helm, `kubectl`, `jq`, and `openssl`. Set the deployment inputs: ```bash export PLATFORM_NAMESPACE='reshapr-system' export CONTROL_PLANE_RELEASE='reshapr-control-plane' export ENCRYPTION_SECRET='reshapr-encryption-key-secret' export CURRENT_KEY_ID='v1' export NEW_KEY_ID='v2' ``` Key identifiers must start with a lowercase letter and contain only lowercase letters and digits. ## Establish a recovery point Run the database owner's backup procedure and restore that backup into an isolated database. Record the tested backup identifier: ```bash export DATABASE_BACKUP_ID='' test -n "${DATABASE_BACKUP_ID}" ``` Record the current Helm values, revision, and active key without reading any key value: ```bash mkdir -p encryption-rotation-evidence helm get values "${CONTROL_PLANE_RELEASE}" \ --namespace "${PLATFORM_NAMESPACE}" \ --output yaml \ > encryption-rotation-evidence/control-plane-values-before.yaml helm history "${CONTROL_PLANE_RELEASE}" \ --namespace "${PLATFORM_NAMESPACE}" \ > encryption-rotation-evidence/helm-history-before.txt export REPORTED_ACTIVE_KEY_ID="$( reshapr admin encryption status --output json \ | jq -er '.activeKid' )" test "${REPORTED_ACTIVE_KEY_ID}" = "${CURRENT_KEY_ID}" ``` Stop if the reported key differs from the reviewed Helm value. Resolve the configuration mismatch before generating or activating another key. ## Generate and store the new key Generate 32 random bytes and store the Base64 value in a protected temporary variable: ```bash set +x NEW_ENCRYPTION_KEY="$(openssl rand -base64 32)" KEY_DATA_NAME="encryption-key-${NEW_KEY_ID}" export NEW_ENCRYPTION_KEY KEY_DATA_NAME ``` Update the externally managed Kubernetes Secret without removing existing entries: ```bash kubectl get secret "${ENCRYPTION_SECRET}" \ --namespace "${PLATFORM_NAMESPACE}" \ --output json \ | jq '.data[env.KEY_DATA_NAME] = (env.NEW_ENCRYPTION_KEY | @base64)' \ | kubectl apply --filename - unset NEW_ENCRYPTION_KEY KEY_DATA_NAME ``` Use the secret manager's normal synchronization workflow instead when it owns this Secret. Never commit key material or pass it through Helm values. ## Phase 1: distribute the complete key set Add the new key to the reviewed control-plane values, but keep the current key active: ```yaml encryptionKey: existingSecret: reshapr-encryption-key-secret activeKeyId: v1 keys: v1: key: encryption-key-v1 v2: key: encryption-key-v2 ``` If you are migrating legacy AES/ECB values, also retain the existing `encryptionKey.key` mapping until every unprefixed value has been migrated. Apply the values and wait for every replica: ```bash helm upgrade "${CONTROL_PLANE_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-control-plane \ --version 0.0.14 \ --namespace "${PLATFORM_NAMESPACE}" \ --values values/control-plane.yaml kubectl rollout status deployment/reshapr-control-plane-ctrl \ --namespace "${PLATFORM_NAMESPACE}" \ --timeout 5m ``` Confirm that all replicas are ready and that `reshapr admin encryption status` still reports `v1`. Do not activate `v2` while any replica is still running without it. ## Phase 2: activate the new key Change only the active key in the reviewed values: ```yaml encryptionKey: existingSecret: reshapr-encryption-key-secret activeKeyId: v2 keys: v1: key: encryption-key-v1 v2: key: encryption-key-v2 ``` Apply a second rollout: ```bash helm upgrade "${CONTROL_PLANE_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-control-plane \ --version 0.0.14 \ --namespace "${PLATFORM_NAMESPACE}" \ --values values/control-plane.yaml kubectl rollout status deployment/reshapr-control-plane-ctrl \ --namespace "${PLATFORM_NAMESPACE}" \ --timeout 5m ``` Verify the active key: ```bash test "$( reshapr admin encryption status --output json \ | jq -er '.activeKid' )" = "${NEW_KEY_ID}" ``` New writes now use `v2`; existing values remain readable through the retained key set. ## Re-encrypt existing values Run the administrator-triggered rotation and save its report: ```bash reshapr admin encryption rotate --yes --output json \ | tee encryption-rotation-evidence/rotation-report.json \ | jq -e ' .secretsRotated >= 0 and .configurationPlansRotated >= 0' ``` The control plane processes up to 200 values per transaction. It rotates Secret `password` and `token` fields, OAuth client secrets nested in `oauth2_client_configuration`, and Configuration Plan API keys. Rows already prefixed with the active key identifier are skipped. Run the command again to verify idempotency: ```bash reshapr admin encryption rotate --yes --output json \ | jq -e ' .secretsRotated == 0 and .configurationPlansRotated == 0' ``` Both counts must be zero. Exercise the known Exposition and its non-destructive Tool call before considering the old key for retirement. ## Retain and retire old keys Keep `v1` available while any of these conditions is true: - rotation has not completed with a zero-count second run; - an application check still depends on data that has not been verified; - a running replica or rollback revision may write with `v1`; - a retained database backup can contain `v1` ciphertext; - the recovery policy requires that backup to be readable without restoring old key material separately. When all retention and recovery requirements permit retirement, remove the old key entry from the reviewed values and external Secret, then roll out every control-plane replica again. Preserve old key material in the approved backup-key escrow when retained backups still require it. ## Recover from an interruption If re-encryption stops after some batches, keep both keys configured, restore control-plane health, and run `reshapr admin encryption rotate --yes` again. The operation skips values already using the active key and resumes the remaining work. If activation causes failures before re-encryption, keep both keys available. You can set `activeKeyId` back to `v1` through another controlled rollout; values already written with `v2` remain readable because `v2` stays in the key set. If a required key was removed, restore it from the secret manager or key escrow before restarting replicas. When database contents are damaged or the required key cannot be recovered, stop writes and use the tested `${DATABASE_BACKUP_ID}` restore procedure with its matching complete key set. Helm rollback alone does not restore database values or key material. ## Result Every control-plane replica knows the complete transition key set, new writes use `v2`, existing supported encrypted fields have been re-encrypted idempotently, and the previous key remains available for the documented recovery window. ## Limits - Rotation is manual; reShapr `1.0.0` does not generate, distribute, schedule, activate, or retire encryption keys. - Rotation covers the sensitive database fields owned by `KeyRotationService`; it is not a general PostgreSQL encryption facility. - The command reports re-encrypted values but does not test database backup restoration. - Removing an old key is irreversible unless that key remains available through an approved recovery system. - Helm rollback does not reverse Flyway migrations, database writes, or external Secret changes. ## Next step Use **[Upgrade reShapr and Rotate Runtime Secrets](./upgrade-and-rotate.md)** for the surrounding release upgrade and other credential rotations. Review **[Security Capabilities and Limits](../../explanations/security-model.md)** for the at-rest encryption boundary. The release-tagged [rotation service](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/security/KeyRotationService.java), [cipher service](https://github.com/reshaprio/reshapr/blob/1.0.0/control-plane/src/main/java/io/reshapr/ctrl/security/CipherService.java), [admin CLI](https://github.com/reshaprio/reshapr/blob/1.0.0/cli/src/commands/admin/encryption.ts), and [Helm chart documentation](https://github.com/reshaprio/reshapr-helm-charts/blob/0.0.14/control-plane/README.md) own the behavior described here. --- ## Troubleshoot an Exposition or Proxy Use this guide when an Exposition has no endpoint, a proxy does not receive a change, or an MCP request fails. Start with the first failing layer and stop when its recovery check succeeds. ## Prerequisites You need: - reShapr runtime `1.0.0` and, for Kubernetes-managed resources, controllers `0.0.3`; - `reshapr login` completed for the affected organization; - access to proxy and operator logs; - `curl`, `jq`, and `kubectl` when the workload runs on Kubernetes; - the Exposition ID, expected Gateway Group, Gateway labels, and MCP URL. Set the values relevant to your deployment: ```bash export EXPOSITION_ID='' export MCP_URL='https:///mcp/' export APP_NAMESPACE='' export PROXY_NAMESPACE='' export PLATFORM_NAMESPACE='reshapr-system' ``` ## Choose the failing layer Run these checks in order: 1. `reshapr expo get "${EXPOSITION_ID}"` must show the expected Gateway Group and at least one endpoint. 2. The proxy readiness endpoint must return `UP`. 3. A `server/discover` request must reach the expected Exposition. 4. `tools/list` must contain the expected Tool. 5. A read-only `tools/call` must reach and be accepted by the backend. An Exposition can be ready in the control plane while no running proxy has registered a matching Gateway. Proxy readiness can also be `UP` while a particular Exposition is absent. Keep these checks separate. ## Exposition has no active endpoint Inspect the Exposition and its target group: ```bash reshapr expo get "${EXPOSITION_ID}" reshapr gateway-group list ``` If no endpoint is listed, compare the target Gateway Group labels with the labels advertised by the intended proxy. For a Kubernetes proxy, inspect the rendered environment: ```bash kubectl get deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --output json \ | jq -r '.spec.template.spec.containers[0].env[] | select(.name == "RESHAPR_GATEWAY_LABELS") | .value' ``` For a standalone container, inspect its startup configuration or logs. A proxy must register a Gateway with labels compatible with the target group. Labels are selection criteria; a mismatch does not set the Exposition or GatewayGroup CR to `ERROR`. Correct either the advertised Gateway labels or the intended Gateway Group, roll out the affected proxy, and wait for registration. Then repeat: ```bash reshapr expo get "${EXPOSITION_ID}" ``` Recovery is complete when the expected hostname advertised by the registered Gateway appears in `ENDPOINTS`. ## Kubernetes resource is not ready When controllers manage the Exposition, inspect every dependency and compare desired and observed generations: ```bash kubectl get services.reshapr.io,gatewaygroups.reshapr.io,configurationplans.reshapr.io,expositions.reshapr.io,secretsources.reshapr.io \ --namespace "${APP_NAMESPACE}" \ --output json | jq -r ' .items[] | [.kind, .metadata.name, .status.status, .metadata.generation, .status.observedGeneration, (.status.message // "")] | @tsv' ``` For `Service`, `GatewayGroup`, `ConfigurationPlan`, `Exposition`, and `SecretSource`, require both: - `status.status` is `READY`; - `status.observedGeneration` equals `metadata.generation`. Use `status.message` to resolve an `ERROR`. An Exposition in `IN_PROGRESS` commonly waits for its Service, ConfigurationPlan, or GatewayGroup to exist remotely. Correct the named dependency first. `CustomTools` and `Resources` use a different status shape in controllers `0.0.3`: ```bash kubectl get customtools.reshapr.io,resources.reshapr.io \ --namespace "${APP_NAMESPACE}" \ --output json | jq -r ' .items[] | [.kind, .metadata.name, .status.state, (.status.message // "")] | @tsv' ``` Controllers chart `0.0.14` packages the controllers `0.0.3` CRDs, but its default image remains `nightly` and its chart metadata still reports app version `0.0.1`. Confirm both the running image and the ConfigurationPlan schema: ```bash kubectl get deployment/reshapr-controllers-operator \ --namespace "${PLATFORM_NAMESPACE}" \ --output jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' kubectl get crd configurationplans.reshapr.io \ --output json \ | jq '.spec.versions[] | select(.name == "v1alpha1") | .schema.openAPIV3Schema.properties.spec.properties | {includedOperations, headerPolicy}' ``` The image must end in `:0.0.3`, and both schema properties must be present. If the image differs, set `operator.image.tag=0.0.3` and restart the operator. If an installation upgraded from an earlier chart still has old schemas, apply the release CRDs explicitly because Helm does not upgrade them automatically. Do not apply `resources.reshapr.io` over controllers `0.0.2`: its kind changed immutably, so follow **[Upgrade reShapr and Rotate Runtime Secrets](./upgrade-and-rotate.md#upgrade-the-web-ui-and-controllers)** for that CRD. Controllers `0.0.3` use the plural `Resources` kind. If Kubernetes reports no match for `kind: Resource`, update the manifest to `kind: Resources` and confirm that the `resources.reshapr.io` CRD also reports `spec.names.kind: Resources`. The old `Unsupported artifact kind and version: Resource - reshapr.io/v1alpha1` error identifies a controllers `0.0.2` operator that still emits the singular artifact kind. If the status does not explain the failure, inspect the operator logs: ```bash kubectl logs \ --namespace "${PLATFORM_NAMESPACE}" \ --selector app.kubernetes.io/component=operator \ --tail 200 ``` After correcting the resource, rerun the first status command. Recovery requires `READY` with matching generations, followed by an endpoint in `reshapr expo get`. ## Proxy is not ready or Gateway is not registered Inspect the workload before changing registration settings: ```bash kubectl get pods --namespace "${PROXY_NAMESPACE}" kubectl logs deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --tail 200 ``` Check, in this order: - DNS and TCP reachability from the proxy to the configured control-plane host and port; - whether the control-plane connection expects TLS or plaintext; - the presence and validity of the Gateway API token; - uniqueness of the Gateway ID among running instances; - syntactic correctness of advertised FQDNs and labels. Port-forward the proxy service and query readiness locally: ```bash kubectl port-forward \ --namespace "${PROXY_NAMESPACE}" \ service/reshapr-proxy 7777:7777 ``` In another terminal: ```bash curl --fail --silent http://localhost:7777/q/health/ready | jq -er '.status' ``` Recovery requires `UP`, followed by the expected endpoint in `reshapr expo get`. Readiness proves initial control-plane connectivity, not backend reachability. Proxies advertise health for their registered Gateways every two minutes. The control plane considers a registration stale after five minutes without an advertisement, and cleanup runs periodically. Do not use the stale-registration window as a readiness test. ## Proxy did not receive a recent change First verify that the Kubernetes generation, when applicable, has been observed and that the control-plane representation has the expected values. Then inspect proxy logs for change-stream or re-registration errors: ```bash kubectl logs deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --since 15m ``` An initialized proxy keeps the last configuration it fetched while synchronization is unavailable. During that interval, new or updated Expositions can be absent and deleted ones can remain locally. Restore proxy-to-control-plane connectivity and wait for stream retry or health-triggered re-registration. If the process cannot recover, perform a controlled rollout after preserving its logs: ```bash kubectl rollout restart deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" kubectl rollout status deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --timeout 5m ``` Recovery requires readiness `UP` and a fresh `server/discover` or `tools/list` response showing the changed MCP surface. ## MCP request returns 400, 401, 403, or 404 Capture the HTTP status, headers, and JSON-RPC body together: ```bash curl --include --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-troubleshooting","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "${MCP_URL}" ``` Use the status and returned error together: | Status | Check | Recovery check | |---|---|---| | `400` | Validate JSON-RPC and the protocol version. For MCP `2026-07-28`, make `Mcp-Method`, optional `Mcp-Name`, and the request body agree. | `server/discover` returns `result.supportedVersions`. | | `401` | Add the configured `x-reshapr-key` or bearer token. For OAuth, check expiry, signature, issuer, and required claims. | The same request succeeds with a valid credential. | | `403` | For OAuth, compare the token resource and service claims with the endpoint, then check the Exposition scopes. | A token with the correct resource and scope succeeds. | | `404` | Check the organization and Exposition path, then verify the requested method exists in the selected protocol mode. | `server/discover` and `tools/list` find the intended surface. | For an audited Configuration Plan, search the exported audit logs for `event.action=authentication`. Its `event.reason` distinguishes failures such as `invalid_api_key`, `missing_bearer_token`, `invalid_token`, or `missing_scope`. ## Tool reaches reShapr but the backend fails A Tool execution failure normally appears in the JSON-RPC result rather than as an endpoint-authentication status. Inspect the full result and correlate it with proxy logs: ```bash kubectl logs deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --since 5m ``` Check the configured backend URL, DNS, egress policy, TLS trust, backend timeout, and backend credential independently. A proxy-generated `504` result indicates that the backend exceeded the configured timeout. Other backend HTTP statuses can be propagated into the Tool result. Run a direct request from an approved diagnostic workload in the same network boundary when policy permits. Do not print credentials or weaken TLS to make the test pass. Recovery requires a read-only `tools/call` whose result is not marked `isError: true` and whose expected backend content is present. ## SecretSource cannot resolve a credential Inspect the aggregate state and each per-Secret condition: ```bash kubectl get secretsource '' \ --namespace "${APP_NAMESPACE}" \ --output json \ | jq '{status: .status.status, generation: .metadata.generation, observed: .status.observedGeneration, message: .status.message, conditions: .status.conditions}' ``` For a `secretRef`, confirm that the Kubernetes Secret exists in the same namespace and contains every named key: ```bash kubectl get secret '' \ --namespace "${APP_NAMESPACE}" \ --output json \ | jq '.data | keys' ``` Do not decode or log the values. Confirm that the operator has its Secret-reader RBAC, then correct the reference or missing key. A Kubernetes Secret update triggers SecretSource reconciliation. Recovery requires the SecretSource to be `READY` with matching generations and successful per-Secret conditions. Finish with a read-only Tool call because reconciliation does not prove that the backend accepts the credential. ## Tool call is waiting for elicitation For stateless MCP `2026-07-28`, an expected elicitation response has `result.resultType=input_required`, one or more `elicitation/create` URLs, and an opaque `requestState`. This is not a Gateway outage. Use a compatible MCP client to open the returned URL over trusted TLS, complete the credential or OAuth flow, and resume the Tool call while preserving `requestState`. Stateless elicitation requires an OAuth-protected Exposition because reShapr associates the value with the token's `iss` and `sub` claims. If the request instead returns `400` for a missing client capability, use a client that declares elicitation support. Clients using a protocol before `2026-07-28` receive the `URL_ELICITATION_REQUIRED` JSON-RPC error and bind the value to their MCP session. When a previously working elicited credential causes a backend `401`, reShapr evicts it. Complete elicitation again with a current credential. Recovery requires the resumed Tool call to return expected backend content. ## Result The first failing layer now has an observable recovery check: reconciled desired state, a matching and ready proxy, a synchronized MCP surface, accepted endpoint credentials, or a successful backend call. ## Limits - A `READY` custom resource proves control-plane reconciliation, not Gateway selection, proxy synchronization, ingress, or backend health. - Proxy readiness proves initial control-plane connectivity, not that every Exposition is loaded or every backend is reachable. - Release `1.0.0` retains the last fetched local registry during synchronization loss; this is not an offline-operation guarantee. - Controllers `0.0.3` do not expose one uniform status contract for all custom resources. - Logs and telemetry depend on the deployment's collection and retention configuration. ## Next step Use **[Observe the reShapr Proxy](./observe-and-audit.md)** to export the signals used here. Review **[Control Plane to Proxy Synchronization](../../explanations/control-plane-gateway-synchronization.md)** for the registration, streaming, heartbeat, and recovery model. The release-tagged [reShapr runtime](https://github.com/reshaprio/reshapr/tree/1.0.0) and [controllers documentation](https://github.com/reshaprio/reshapr-controllers/tree/0.0.3/documentation) remain the canonical behavioral references. --- ## Upgrade reShapr and Rotate Runtime Secrets Use this runbook to upgrade an existing Kubernetes deployment to reShapr `1.0.0`, controllers `0.0.3`, and Helm charts `0.0.14`. It also covers control-plane database encryption keys, an Exposition API key, a Gateway registration token, and a backend credential referenced through `${env:...}`. This is not a universal upgrade path from every earlier release. Validate the exact source-to-target path in staging before changing production. ## Prerequisites You need: - the four reShapr releases installed as described in **[Deploy reShapr on Kubernetes for Production](../deploy-kubernetes-production.md)**; - tracked and reviewable Helm values for every installed release; - access to the [reShapr `1.0.0` release](https://github.com/reshaprio/reshapr/releases/tag/1.0.0), [controllers `0.0.3`](https://github.com/reshaprio/reshapr-controllers/releases/tag/0.0.3), and [charts `0.0.14`](https://github.com/reshaprio/reshapr-helm-charts/releases/tag/0.0.14); - an externally managed PostgreSQL service with a tested backup and restore procedure; - maintenance authority for Gateway registration and client credentials; - one active Exposition and one non-destructive Tool for post-upgrade checks; - Helm, `kubectl`, `curl`, `jq`, `openssl`, and reShapr CLI `1.0.0`. Set the release names and namespaces used by this runbook: ```bash export PLATFORM_NAMESPACE='reshapr-system' export PROXY_NAMESPACE='reshapr-proxies' export CONTROL_PLANE_RELEASE='reshapr-control-plane' export WEB_UI_RELEASE='reshapr-ui' export CONTROLLERS_RELEASE='reshapr-controllers' export PROXY_RELEASE='reshapr-proxy' export TARGET_CHART_VERSION='0.0.14' export TARGET_RUNTIME_VERSION='1.0.0' export TARGET_CONTROLLERS_VERSION='0.0.3' export MCP_URL='https:///mcp//' export EXPOSITION_ID='' ``` ## Review the upgrade before applying it Read both target release notes. Check for compatibility requirements, removed values, changed defaults, database migrations, and manual steps. Do not infer runtime compatibility from the chart version alone. Record the installed releases and user-supplied values: ```bash mkdir -p upgrade-evidence helm list --all-namespaces > upgrade-evidence/helm-list-before.txt helm get values "${CONTROL_PLANE_RELEASE}" --namespace "${PLATFORM_NAMESPACE}" --output yaml \ > upgrade-evidence/control-plane-values-before.yaml helm get values "${WEB_UI_RELEASE}" --namespace "${PLATFORM_NAMESPACE}" --output yaml \ > upgrade-evidence/web-ui-values-before.yaml helm get values "${CONTROLLERS_RELEASE}" --namespace "${PLATFORM_NAMESPACE}" --output yaml \ > upgrade-evidence/controllers-values-before.yaml helm get values "${PROXY_RELEASE}" --namespace "${PROXY_NAMESPACE}" --output yaml \ > upgrade-evidence/proxy-values-before.yaml ``` Inspect the target defaults next to your tracked values: ```bash helm show values \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-control-plane \ --version "${TARGET_CHART_VERSION}" \ > upgrade-evidence/control-plane-target-defaults.yaml helm show values \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-proxy \ --version "${TARGET_CHART_VERSION}" \ > upgrade-evidence/proxy-target-defaults.yaml ``` Repeat `helm show values` for installed optional charts. Merge every required current override into the tracked target values rather than relying on `--reuse-values` across a changed values schema. When upgrading from charts older than `0.0.12`, prefer changing `gateway.controlPlane.token` proxy value with `gateway.controlPlane.existingSecret` and `gateway.controlPlane.tokenKey`. Never copy secret values into a values file. Pin these image fields in the reviewed files: | File | Field | Target | |---|---|---| | `values/control-plane.yaml` | `ctrl.image.tag` | `1.0.0` | | `values/web-ui.yaml` | `image.tag` | `1.0.0` | | `values/proxy.yaml` | `image.tag` | `1.0.0` | | `values/controllers.yaml` | `operator.image.tag` | `0.0.3` | | `values/controllers.yaml` | `admissionController.image.tag` | `0.0.3` | Before upgrading from a release that used one AES/ECB key, add a new Base64-encoded 32-byte AES key to the existing Kubernetes Secret while retaining the old value under its current key: | Kubernetes Secret key | Purpose during migration | |---|---| | `encryption-key-v1` | AES-256-GCM key for new writes | | `encryption-key` | Legacy AES/ECB key needed to read and migrate existing values | Configure the target control-plane values to activate the new key and keep the legacy key mapping: ```yaml encryptionKey: existingSecret: reshapr-encryption-key-secret activeKeyId: v1 keys: v1: key: encryption-key-v1 key: encryption-key ``` Generate and distribute the key through your secret manager. All control-plane replicas must receive the complete key set before any replica starts writing with the new active key. Render and review each target release with `helm template` in staging or CI before proceeding. ## Establish the recovery point Run the database owner's backup procedure and restore that backup into an isolated database. Record the successful backup identifier and restore test: ```bash export DATABASE_BACKUP_ID='' test -n "${DATABASE_BACKUP_ID}" ``` The charts do not create or test PostgreSQL backups. Do not continue if the restore has not been exercised for this upgrade. Capture the current Helm revisions and baseline behavior: ```bash helm history "${CONTROL_PLANE_RELEASE}" --namespace "${PLATFORM_NAMESPACE}" helm history "${PROXY_RELEASE}" --namespace "${PROXY_NAMESPACE}" reshapr info reshapr expo get "${EXPOSITION_ID}" ``` Send a `server/discover` request and one known read-only Tool call. Retain their non-sensitive outcomes for comparison after the upgrade. When proxy clustering is enabled with the chart-generated keystore, record the mounted Secret name and UID without reading its data: ```bash export PROXY_KEYSTORE_SECRET="$( kubectl get deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --output json \ | jq -er '.spec.template.spec.volumes[] | select(.name == "cluster-keystore") | .secret.secretName' )" export PROXY_KEYSTORE_UID="$( kubectl get secret "${PROXY_KEYSTORE_SECRET}" \ --namespace "${PROXY_NAMESPACE}" \ --output jsonpath='{.metadata.uid}' )" ``` Skip this check when clustering is disabled. The generated Secret has Helm's `keep` policy and must be reused by every pod in the rolling upgrade. ## Upgrade the control plane The control plane runs Flyway migrations at startup. Upgrade it first and wait for readiness before changing its clients: ```bash helm upgrade "${CONTROL_PLANE_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-control-plane \ --version "${TARGET_CHART_VERSION}" \ --namespace "${PLATFORM_NAMESPACE}" \ --values values/control-plane.yaml kubectl rollout status deployment/reshapr-control-plane-ctrl \ --namespace "${PLATFORM_NAMESPACE}" \ --timeout 5m ``` Inspect startup output for Flyway or database errors, then check readiness and the reported runtime version: ```bash kubectl logs deployment/reshapr-control-plane-ctrl \ --namespace "${PLATFORM_NAMESPACE}" \ --since 15m curl --fail --silent https:///q/health/ready | jq -er '.status' reshapr info ``` Stop the rollout if readiness fails or the server does not report `1.0.0`. Preserve logs and database state before attempting recovery. ## Upgrade the Web UI and controllers Upgrade only the optional releases you have installed: ```bash helm upgrade "${WEB_UI_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-web-ui \ --version "${TARGET_CHART_VERSION}" \ --namespace "${PLATFORM_NAMESPACE}" \ --values values/web-ui.yaml ``` When upgrading controllers `0.0.2` to `0.0.3`, the Kubernetes kind changes from singular `Resource` to plural `Resources`. Kubernetes treats a CRD's `spec.names.kind` as immutable, and Helm does not upgrade CRDs. Before upgrading the controllers release: 1. Stop GitOps synchronization for `Resource` manifests and change their `kind` to `Resources` in Git. 2. Back up every existing object: ```bash kubectl get resources.reshapr.io --all-namespaces --output yaml \ > upgrade-evidence/resources-before.yaml ``` 3. Confirm that Git contains a restorable manifest for every backed-up object, then replace the CRD: ```bash kubectl delete customresourcedefinition resources.reshapr.io kubectl apply --filename \ "https://raw.githubusercontent.com/reshaprio/reshapr-controllers/${TARGET_CONTROLLERS_VERSION}/deploy/crd/resources.reshapr.io-v1.yml" kubectl get customresourcedefinition resources.reshapr.io \ --output jsonpath='{.spec.names.kind}{"\n"}' ``` Deleting the CRD deletes its existing custom resources. The final command must print `Resources`. Upgrade the controllers before resuming GitOps: ```bash helm upgrade "${CONTROLLERS_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-controllers \ --version "${TARGET_CHART_VERSION}" \ --namespace "${PLATFORM_NAMESPACE}" \ --values values/controllers.yaml ``` Resume GitOps only after the `0.0.3` operator is running. Recreated objects reconcile the remote artifact again. Wait for their workloads and inspect reconciled resources: ```bash kubectl get pods --namespace "${PLATFORM_NAMESPACE}" \ --selector app.kubernetes.io/instance="${WEB_UI_RELEASE}" kubectl get pods --namespace "${PLATFORM_NAMESPACE}" \ --selector app.kubernetes.io/instance="${CONTROLLERS_RELEASE}" kubectl get services.reshapr.io,gatewaygroups.reshapr.io,configurationplans.reshapr.io,expositions.reshapr.io,secretsources.reshapr.io \ --all-namespaces ``` Helm retains CRDs and does not treat them like ordinary release templates. Except for the explicit `Resource` to `Resources` migration above, never delete a CRD as an upgrade or rollback step: deletion removes every custom resource of that kind across namespaces. Apply release-specific CRD changes before relying on new schema fields. ## Upgrade the proxies Apply the reviewed proxy values and wait for the rollout: ```bash helm upgrade "${PROXY_RELEASE}" \ oci://quay.io/reshapr/reshapr-helm-charts/reshapr-proxy \ --version "${TARGET_CHART_VERSION}" \ --namespace "${PROXY_NAMESPACE}" \ --values values/proxy.yaml kubectl rollout status deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --timeout 5m curl --fail --silent https:///q/health/ready | jq -er '.status' ``` If you recorded a chart-generated clustering keystore, verify that the same Secret survived: ```bash test "$( kubectl get secret "${PROXY_KEYSTORE_SECRET}" \ --namespace "${PROXY_NAMESPACE}" \ --output jsonpath='{.metadata.uid}' )" = "${PROXY_KEYSTORE_UID}" ``` Do not delete or regenerate this Secret during a rolling upgrade. Rotating the clustering key is a separate, disruptive operation because all members must restart with the same key. ## Verify the upgraded path Confirm the Exposition still lists the expected Gateway endpoint: ```bash reshapr expo get "${EXPOSITION_ID}" ``` Discover the MCP server through its public route: ```bash curl --fail --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-upgrade-check","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "${MCP_URL}" | jq -er '.result.supportedVersions' ``` Repeat the baseline read-only Tool call and compare its result. Also inspect error rate, latency, proxy logs, and audit signals through at least one normal telemetry interval before closing the maintenance window. ## Recover from a failed upgrade Use `helm history` to identify the previous release revision. For Web UI, controllers, or proxy manifest failures, roll back the affected release and repeat its health checks: ```bash helm rollback '' '' \ --namespace '' \ --wait ``` Do not blindly apply this command to a failed control-plane upgrade. `helm rollback` reapplies Kubernetes manifests, but it does not reverse Flyway migrations, restore database contents, or downgrade CRDs. For a control-plane failure: 1. stop application writes according to your incident procedure; 2. determine from the release notes whether the previous runtime is compatible with the migrated schema; 3. if it is compatible, roll back the control-plane Helm revision and verify it; 4. if it is not compatible, follow the database owner's tested restore procedure for `${DATABASE_BACKUP_ID}` and restore the matching Helm revision; 5. verify control-plane readiness, proxy readiness and Gateway registration, the Exposition, and the read-only Tool call. These are manual recovery decisions. reShapr does not provide automated application rollback, schema rollback, or database restore. ## Rotate the database encryption key Follow **[Rotate the Database Encryption Key](./rotate-database-encryption-key.md)** after the release upgrade. Its two-phase rollout first distributes the complete key set to every replica, then changes the active key before invoking idempotent re-encryption. Keep old AES-GCM keys in `encryptionKey.keys` while any database backup or replica may still contain values encrypted with them. Remove `encryptionKey.key` and the legacy Secret entry only after migration, application checks, backup retention, and recovery requirements all permit it. ## Rotate an Exposition API key Renew the key on the Configuration Plan used by the Exposition: ```bash export CONFIGURATION_PLAN_ID='' reshapr config renew-api-key "${CONFIGURATION_PLAN_ID}" ``` The CLI displays the new key once. Store it in the client secret manager immediately and treat the previous key as invalid. Update authorized MCP clients, verify that the new key succeeds, and verify that the old key receives HTTP `401`. There is no documented overlap or scheduled API-key rotation mechanism in `1.0.0`. Coordinate clients before renewal when an immediate cutover would interrupt them. ## Rotate a Gateway registration token Create a replacement token without deleting the current one: ```bash reshapr api-token create 'prod-gateway-rotation' --validity-days 30 ``` Store the displayed value immediately. Update the `token` key in the Kubernetes Secret referenced by `gateway.controlPlane.existingSecret`. Use your secret manager's normal synchronization path; for a controlled manual update, read the value without adding it to shell history: ```bash read -r -s -p 'Replacement Gateway token: ' NEW_GATEWAY_TOKEN printf '\n' printf '%s' "${NEW_GATEWAY_TOKEN}" \ | kubectl create secret generic reshapr-gateway-token \ --namespace "${PROXY_NAMESPACE}" \ --from-file=token=/dev/stdin \ --dry-run=client \ --output yaml \ | kubectl apply --filename - unset NEW_GATEWAY_TOKEN kubectl rollout restart deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" kubectl rollout status deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --timeout 5m ``` Require readiness `UP`, an endpoint in `reshapr expo get`, and a successful MCP discovery. Then list tokens, identify the old token by ID, and revoke it: ```bash reshapr api-token list reshapr api-token delete '' ``` Repeat this sequence for every proxy release that uses the old token. Token creation, workload replacement, verification, and old-token revocation are operator-managed steps. ## Rotate a backend `${env:...}` credential When a control-plane Secret stores a reference such as `${env:BACKEND_API_TOKEN}`, update `BACKEND_API_TOKEN` in the secret manager that supplies the proxy workload. Keep the reference itself unchanged. Kubernetes environment variables are fixed for the lifetime of a container. Trigger a rollout so new pods receive the new value: ```bash kubectl rollout restart deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" kubectl rollout status deployment/reshapr-proxy \ --namespace "${PROXY_NAMESPACE}" \ --timeout 5m ``` Verify proxy readiness and Gateway registration, then repeat a read-only Tool call that requires the backend credential. Revoke the previous backend credential only after the new value is accepted. Coordinate an overlap in the backend credential system when uninterrupted calls are required. ## Result The Helm releases use charts `0.0.14`, runtime workloads use `1.0.0`, controllers use `0.0.3`, the database recovery point remains external and tested, and each rotated key or credential has an explicit replacement and verification step. ## Limits - Flyway migrations run at control-plane startup. Helm rollback cannot reverse them or restore data. - PostgreSQL backup, restore, retention, and recovery testing belong to the database operator. - Helm does not provide automatic CRD downgrade or deletion during rollback. - The generated proxy clustering keystore is retained across upgrades; rotating it requires a separately planned simultaneous restart. - Database encryption-key rotation is administrator-triggered; key generation, distribution, activation, retirement, and scheduling remain external responsibilities. - API-key, Gateway-token, and `${env:...}` credential rotations are not scheduled or automated by reShapr `1.0.0`. - A successful rollout does not by itself validate ingress, Exposition propagation, endpoint authorization, or backend behavior. ## Next step Use **[Troubleshoot an Exposition or Proxy](./troubleshoot.md)** when a post-upgrade check fails, and **[Observe the reShapr Proxy](./observe-and-audit.md)** to compare telemetry across the maintenance window. The release-tagged [Helm chart documentation](https://github.com/reshaprio/reshapr-helm-charts/tree/0.0.14) and [reShapr runtime](https://github.com/reshaprio/reshapr/tree/1.0.0) remain the canonical references. --- ## Protect an MCP Endpoint with an API Key An API key restricts the client-to-proxy boundary of an MCP endpoint. reShapr stores the key on the Configuration Plan and applies it to every Exposition created from that plan. ## Prerequisites - reShapr CLI `1.0.0`, authenticated with `reshapr login` - A Service ID and its backend endpoint - `curl` and `jq` ## Create a protected Configuration Plan Create the plan with `--apiKey` and capture its structured output: ```bash CONFIG_JSON="$( reshapr config create 'protected-open-meteo' \ --serviceId '' \ --backendEndpoint 'https://api.open-meteo.com' \ --apiKey \ --output json )" ``` The JSON response contains the generated Configuration Plan ID and API key. Extract both without putting the key in shell history: ```bash RESHAPR_CONFIG_ID="$(jq -er '.id' <<<"$CONFIG_JSON")" RESHAPR_API_KEY="$(jq -er '.apiKey' <<<"$CONFIG_JSON")" export RESHAPR_CONFIG_ID RESHAPR_API_KEY unset CONFIG_JSON ``` Create an Exposition from this plan and set the returned endpoint URL: ```bash reshapr expo create --configuration "$RESHAPR_CONFIG_ID" --gateway-group 1 export MCP_URL='https:///mcp/' ``` ## Verify that the key is required Send a stateless discovery request without the key and print its HTTP status: ```bash curl --silent --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" ``` The expected status is `401`. Repeat the request with the key: ```bash curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "x-reshapr-key: $RESHAPR_API_KEY" \ --data '{"jsonrpc":"2.0","id":2,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" | jq '.result.supportedVersions' ``` A JSON-RPC result containing the supported versions confirms that the key was accepted. ## Rotate the key Keep the current value so that you can verify its revocation, then generate a replacement: ```bash export RESHAPR_OLD_API_KEY="$RESHAPR_API_KEY" RESHAPR_API_KEY="$( reshapr config renew-api-key "$RESHAPR_CONFIG_ID" --output json \ | jq -er '.apiKey' )" export RESHAPR_API_KEY ``` The Gateway receives the Configuration Plan update without a restart. Confirm that the old key is rejected: ```bash curl --silent --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "x-reshapr-key: $RESHAPR_OLD_API_KEY" \ --data '{"jsonrpc":"2.0","id":3,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-curl","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}' \ "$MCP_URL" ``` The expected status is `401`. Repeat the authenticated discovery request with `$RESHAPR_API_KEY`; it must succeed. ## Result The endpoint rejects requests without the current key, accepts requests with it, and stops accepting the previous key after rotation. ## Limits - The key protects MCP client access to the Gateway. It does not authenticate Gateway requests to the backend API; configure a Backend Secret for that boundary. - Anyone holding the key can access every Exposition derived from the Configuration Plan. Use separate plans when consumers need separate credentials. - API keys do not provide user identity or scopes. Use OAuth 2.0 when the endpoint requires those controls. - TLS is a deployment concern. Serve protected endpoints over HTTPS so the key is encrypted in transit. ## Next step - **[Test an MCP endpoint](../test-mcp-endpoint.md)** to list and call its Tools with the key. - **[Security Capabilities and Limits](../../explanations/security-model.md)** compares the independent authentication boundaries. --- ## Authenticate Backend Calls and Use Elicitation Backend authentication controls the proxy-to-backend boundary. It is independent from the API key or OAuth policy that protects an MCP endpoint. Use this guide to choose a backend Secret, attach it to a Configuration Plan, and verify a non-destructive Tool call. The main procedure uses a local environment reference so that the credential value does not enter control-plane storage. :::tip Watch the security flows See [token elicitation with GitHub GraphQL](https://youtu.be/y38__Uj5gWo), [OAuth URL elicitation with Keycloak](https://youtu.be/dC41-ieQIqk), and [separate endpoint and backend OAuth flows](https://youtu.be/_DTf34OaLr0). Use this guide for the verified configuration and security boundaries. ::: ## Prerequisites You need: - the reShapr `1.0.0` CLI, authenticated with `reshapr login`; - an imported Service for a protected test backend; - a Gateway Group and a proxy instance whose runtime configuration you control; - a read-only backend operation and its expected successful response; - `curl` and `jq`; - the backend credential stored in a secret manager rather than shell history. Set the non-sensitive inputs: ```bash export SERVICE_ID='' export BACKEND_ENDPOINT='https://api.example.com' export GATEWAY_GROUP_ID='' ``` ## Choose a backend authentication mode | Backend | Secret fields | Proxy behavior | |---|---|---| | REST or GraphQL with bearer token | `token` | Sends `Authorization: Bearer ` | | REST or GraphQL with API key | `token` and `tokenHeader` | Sends the token through the named HTTP header | | REST or GraphQL with Basic authentication | `username` and `password` | Sends HTTP Basic credentials | | gRPC with token | `token`, optionally `tokenHeader` | Adds per-call gRPC metadata | | gRPC with a private CA | `certPem` | Uses the PEM certificate as a custom TLS trust anchor | | REST, GraphQL, or gRPC with OAuth Client Credentials | `authMethod: OAUTH2_CLIENT_CREDENTIALS` and an OAuth client configuration | Obtains and caches a machine-to-machine access token | | REST, GraphQL, or gRPC with elicitation | `useElicitation` and a header or OAuth client configuration | Requests a credential for the current MCP session or authenticated user | In release `1.0.0`, username/password is not applied as gRPC Basic authentication, and `certPem` is not used by the HTTP proxy. The certificate field configures gRPC server trust; it is not a client certificate. ## Create a locally resolved Secret **[Secret references](../../explanations/security-model.md#secret-references)** explains why the control plane stores a placeholder while the target proxy resolves the credential locally. Read the backend token into the shell environment without echoing it: ```bash read -r -s -p 'Backend API token: ' BACKEND_API_TOKEN export BACKEND_API_TOKEN printf '\n' ``` Create a backend Secret containing a reference, not the token value. Keep the single quotes so the shell does not expand the placeholder: ```bash export BACKEND_SECRET_ID="$( reshapr secret create hybrid-backend-token \ --backend \ --token '${env:BACKEND_API_TOKEN}' \ --description 'Resolved by the target proxy' \ --output json \ | jq -er '.id' )" ``` For a backend API key carried by a custom header, add `--tokenHeader ''`. For HTTP Basic authentication, replace the token option with: ```bash --username '${env:BACKEND_USERNAME}' \ --password '${env:BACKEND_PASSWORD}' ``` The proxy resolves each placeholder when preparing a backend call. Release `1.0.0` provides the `env` scheme; an unknown scheme or missing value fails the call. ### Make the value available to the proxy Inject the variable through the workload's secret mechanism. For the Docker command in **[Deploy a Hybrid reShapr Proxy](../deploy-hybrid-gateway.md)**, add this option when creating the container: ```bash --env BACKEND_API_TOKEN ``` This form passes the value from the current environment without placing it in the `docker run` arguments. For Kubernetes, map a Secret key to an environment variable in the proxy container instead of committing the value to a manifest. Check the proxy logs after startup. A missing variable is reported when the first matching backend call tries to resolve it. ### Attach the Secret to a Configuration Plan Create a Configuration Plan for the protected backend: ```bash export RESHAPR_CONFIG_ID="$( reshapr config create 'local-backend-auth' \ --serviceId "${SERVICE_ID}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --backendSecret "${BACKEND_SECRET_ID}" \ --output json \ | jq -er '.id' )" ``` Endpoint access defaults to no authentication in this example. Protect it with **[an API key](./api-key.md)** or **[OAuth 2.0](./oauth.md)** before allowing untrusted clients to reach it. Create and inspect a named Exposition: ```bash export EXPOSITION_ID="$( reshapr expo create \ --configuration "${RESHAPR_CONFIG_ID}" \ --gateway-group "${GATEWAY_GROUP_ID}" \ --name backend-auth-check \ --output json \ | jq -er '.id' )" reshapr expo get "${EXPOSITION_ID}" ``` ### Verify the backend call Set the exact endpoint returned by `reshapr expo get` and select a read-only Tool: ```bash export MCP_URL='https:///mcp//backend-auth-check' export TOOL_NAME='' export TOOL_ARGUMENTS='{}' ``` Call the Tool: ```bash jq -n \ --arg name "${TOOL_NAME}" \ --argjson arguments "${TOOL_ARGUMENTS}" \ '{jsonrpc:"2.0",id:1,method:"tools/call",params:{name:$name,arguments:$arguments,_meta:{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{name:"reshapr-backend-auth-check",version:"0.2.3"},"io.modelcontextprotocol/clientCapabilities":{}}}}' | \ curl --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header "Mcp-Name: ${TOOL_NAME}" \ --data @- \ "${MCP_URL}" | jq '.result' ``` An expected backend response confirms that the proxy resolved the reference and applied the credential. A backend `401` usually means the value is missing, expired, or sent through the wrong header. ### Rotate a local value reShapr does not cache the resolved Secret value between backend calls. Whether a changed value becomes visible without replacing the proxy depends on the configuration source. Environment variables of an existing Docker container cannot be changed in place. To rotate this example: 1. replace `BACKEND_API_TOKEN` in the secret manager or deployment environment; 2. recreate the proxy container with the same Gateway ID, labels, and `--env BACKEND_API_TOKEN` option; 3. wait for readiness and registration; 4. repeat the read-only Tool call and confirm that the backend accepts the new value; 5. revoke the previous backend token. The Secret stored by the control plane remains `${env:BACKEND_API_TOKEN}` throughout this rotation. Kubernetes workloads likewise need a rollout when a Secret is consumed as an environment variable. ## Use direct credential elicitation Use elicitation when each MCP user must provide a backend credential instead of sharing one provisioned for the proxy. Stateless elicitation with MCP `2026-07-28` requires an OAuth-protected Exposition because reShapr associates the elicited value with the bearer token's `iss` and `sub` claims. Create an elicitation Secret whose `--token` option names the backend header that will receive the user-provided value: ```bash export ELICITATION_SECRET_ID="$( reshapr secret create-elicitation user-backend-key \ --token 'X-API-Key' \ --description 'Request one backend API key per MCP user' \ --output json \ | jq -er '.id' )" ``` Create an OAuth-protected Configuration Plan as described in **[Protect an MCP Endpoint with OAuth 2.0](./oauth.md)** and add: ```bash --backendSecret "${ELICITATION_SECRET_ID}" ``` Create an Exposition from that plan. Call a Tool with a valid endpoint bearer token before providing the backend credential. The JSON-RPC result has this shape: ```json { "result": { "resultType": "input_required", "inputRequests": { "": { "method": "elicitation/create", "params": { "mode": "url", "elicitationId": "", "url": "https:///elicitation/form?elicitationId=" } } }, "requestState": "" } } ``` Open the supplied URL over a trusted TLS connection, enter the backend credential, and let the MCP client resume the Tool call while preserving the opaque `requestState`. A successful backend response confirms completion. If the backend later returns `401`, the Gateway evicts the elicited value so that the user can provide a replacement. Clients using a protocol before `2026-07-28` receive a `URL_ELICITATION_REQUIRED` JSON-RPC error instead. The elicited value is then associated with the MCP session rather than the authenticated `iss` and `sub` identity. ## Use OAuth elicitation for the backend An elicitation Secret can redirect the user through a backend Authorization Server instead of displaying a token form: ```bash reshapr secret create-elicitation backend-oauth \ --oauth2ClientID '' \ --oauth2ClientSecret '${env:BACKEND_OAUTH_CLIENT_SECRET}' \ --oauth2AuthorizationEndpoint 'https://idp.example.com/authorize?scope=backend.read' \ --oauth2TokenEndpoint 'https://idp.example.com/token' ``` Register `https:///elicitation/callback` as an allowed redirect base with the backend Authorization Server. The proxy adds the elicitation identifier, `client_id`, `redirect_uri`, `response_type=code`, and stateless `state` parameters. It resolves the optional client-secret reference locally before exchanging the authorization code. This flow cannot be validated without a real Authorization Server, a compatible MCP client, and a callback URL reachable through the proxy. Test it in an isolated identity-provider tenant before production use. ## Use OAuth Client Credentials Use this mode when the proxy must authenticate as a workload rather than as the MCP user. **[Use OAuth Client Credentials for Backend Calls](./use-oauth-client-credentials.md)** covers the proxy-local client secret, token request, cache behavior, backend verification, rotation, and recovery procedure. ## Roll back Delete resources in dependency order: ```bash reshapr expo delete "${EXPOSITION_ID}" reshapr config delete "${RESHAPR_CONFIG_ID}" reshapr secret delete "${BACKEND_SECRET_ID}" ``` Delete any additional elicitation Secrets and their Configuration Plans after removing the Expositions that use them. Revoke test credentials and OAuth clients in their owning systems. ## Result The proxy authenticates a read-only backend call with a locally resolved credential. You can distinguish that shared runtime credential from a user-specific elicited credential and from the independent policy protecting the MCP endpoint. ## Limits - Release `1.0.0` provides only the `env` local-reference scheme. It does not integrate directly with a general external secret-provider API. - Client Credentials is a shared machine identity, not user delegation. Refresh tokens returned by an Authorization Server are ignored; the proxy requests a new access token when needed. - Environment-variable rotation requires the workload platform to make the new value visible. Docker and Kubernetes environment variables require container or pod replacement. - HTTP Basic credentials are not applied to gRPC calls. Custom CA certificates are applied to gRPC TLS, not HTTP backends, and are not client certificates. - Elicited credentials are runtime values associated with a session or authenticated user. They are not a substitute for MCP endpoint authentication or authorization. - Elicitation requires a compatible client. OAuth elicitation additionally requires an Authorization Server and a reachable callback route. ## Next step - **[Security Capabilities and Limits](../../explanations/security-model.md)** compares the controls and their boundaries. - **[Deploy a Hybrid reShapr Proxy](../deploy-hybrid-gateway.md)** shows where to inject proxy-local environment variables. - **[Test an MCP Endpoint](../test-mcp-endpoint.md)** covers stateless and session-based MCP requests. --- ## Configure Backend Request Header Policy Use a request header policy when an HTTP or gRPC backend needs selected MCP client headers, such as a trace identifier, while other client-supplied headers must be removed or renamed. For gRPC calls, surviving headers become call metadata. The policy applies at the proxy-to-backend boundary; it does not authorize an MCP operation or replace backend authorization. This guide shows the CLI and Kubernetes forms of the same policy, then verifies the headers observed by a test backend. Use only one ownership path for a given Configuration Plan. ## Prerequisites You need: - reShapr `1.0.0` and its CLI; - for the Kubernetes path, controllers `0.0.3`; - an imported REST, GraphQL, or gRPC Service; - an OAuth-protected Configuration Plan or the inputs needed to create one; - a test backend operation that returns or records received request headers; - an Exposition for the Configuration Plan; - `curl` and `jq`. Set the identifiers used by the CLI example: ```bash export SERVICE_ID='' export BACKEND_ENDPOINT='https://echo-api.example.com' export AUTHORIZATION_SERVER='https://idp.example.com' export JWKS_URI='https://idp.example.com/.well-known/jwks.json' ``` ## Choose the request rules Request rules are case-insensitive and have three roles: - `allow` explicitly forwards a named header and can re-enable the normally protected `Authorization` or `Cookie` header; - `deny` removes additional headers; - `rename` moves a forwarded source header to another name after filtering. The proxy always removes transport and internal headers before applying these rules: ```text Host, Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade, Content-Length, X-Reshapr-Key, MCP-Session-Id, MCP-Protocol-Version, Mcp-Method, Mcp-Name ``` An `allow` rule cannot restore one of those headers. Without an explicit `allow`, the proxy also removes `Authorization` and `Cookie`. To pass a client credential under another backend header, prefer an explicit rename such as `X-Backend-Authorization` to `Authorization` rather than forwarding every client header. ## Configure the policy with the CLI Create an OAuth-protected Configuration Plan and pass the request rules as one JSON object: ```bash export CONFIGURATION_PLAN_ID="$( reshapr config create-oauth header-policy-check \ --serviceId "${SERVICE_ID}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --oauth2AuthorizationServers "[\"${AUTHORIZATION_SERVER}\"]" \ --oauth2jwksUri "${JWKS_URI}" \ --requestHeaderPolicy '{ "allow": ["X-Trace-Id"], "deny": ["X-Internal-Only"], "rename": ["X-Backend-Authorization:Authorization"] }' \ --output json \ | jq -er '.id' )" ``` :::tip Authorization passthrough The CLI option `--passthrough` is shorthand for a request policy containing `allow: ["Authorization"]`. It forwards the MCP client's `Authorization` header to the backend and is mutually exclusive with `--requestHeaderPolicy`. Use it only when the backend is intentionally meant to receive the same bearer credential as the MCP endpoint. Otherwise, keep endpoint and backend credentials separate and configure an explicit backend Secret. ::: Inspect the resulting Plan: ```bash reshapr config get "${CONFIGURATION_PLAN_ID}" --output json \ | jq -e ' .headerPolicy.request.allow == ["X-Trace-Id"] and .headerPolicy.request.deny == ["X-Internal-Only"] and .headerPolicy.request.rename == [ {"from":"X-Backend-Authorization","to":"Authorization"} ]' ``` ## Configure the policy with Kubernetes For a controller-owned Configuration Plan, express the same rules under `spec.headerPolicy.request`: ```yaml apiVersion: reshapr.io/v1alpha1 kind: ConfigurationPlan metadata: name: header-policy-check spec: headerPolicy: request: allow: - X-Trace-Id deny: - X-Internal-Only rename: - from: X-Backend-Authorization to: Authorization ``` Add this block to a complete Configuration Plan manifest whose Service, backend endpoint, endpoint OAuth policy, and Secret references are already defined. Apply it through the same GitOps workflow that owns the Plan, then wait for its status to become `READY`. The controller API also reserves `spec.headerPolicy.response`. In controllers `0.0.3` and reShapr `1.0.0`, response rules are not enforced by the proxy. Do not configure them as a security control. ## Verify the forwarded headers Create an Exposition for the Plan as described in **[Protect an MCP Endpoint with OAuth 2.0](./oauth.md)**. Set its URL, a read-only Tool backed by the echo service, and a valid endpoint access token: ```bash export MCP_URL='https:///mcp//' export TOOL_NAME='' export MCP_ACCESS_TOKEN='' ``` Call the Tool with one allowed header, one denied header, and one renamed header: ```bash jq -n \ --arg name "${TOOL_NAME}" \ '{ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: $name, arguments: {}, _meta: { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": { name: "reshapr-header-policy-check", version: "1.0.0" }, "io.modelcontextprotocol/clientCapabilities": {} } } }' | \ curl --silent --show-error --fail-with-body \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: tools/call' \ --header "Mcp-Name: ${TOOL_NAME}" \ --header "Authorization: Bearer ${MCP_ACCESS_TOKEN}" \ --header 'X-Trace-Id: trace-policy-check' \ --header 'X-Internal-Only: must-not-arrive' \ --header 'X-Backend-Authorization: backend-policy-check' \ --data @- \ "${MCP_URL}" | jq '.result' ``` In the backend result or access log, verify all three postconditions: - `X-Trace-Id` is `trace-policy-check`; - `X-Internal-Only` is absent; - `Authorization` is `backend-policy-check`, and `X-Backend-Authorization` is absent. Also confirm that MCP transport headers such as `MCP-Protocol-Version` did not reach the backend. ## Limits - For gRPC backends, the same request policy applies before headers become call metadata. The proxy additionally removes `Accept`, `Content-Type`, and `User-Agent`, which the gRPC transport manages. - Response header rules are reserved but not enforced in reShapr `1.0.0`. - Explicitly allowing `Authorization` or `Cookie` transfers their security impact to the backend. - Rename rules express an intentional trust transition and can target a normally denied header such as `Authorization`. - Header filtering does not restrict which Tools are visible or callable. ## Next step Use **[Authenticate Backend Calls and Use Elicitation](./backend-auth-and-elicitation.md)** when the backend credential should come from a reShapr Secret instead of an MCP client header. Review **[Configuration Plans and Expositions](../../explanations/configuration-and-exposition.md)** for the wider composition model. The release-tagged [header policy engine](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/proxy/HeaderPolicyEngine.java), [gRPC proxy integration](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/proxy/GrpcProxyService.java), [runtime tests](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/test/java/io/reshapr/proxy/proxy/HeaderPolicyEngineTest.java), and [controllers API](https://github.com/reshaprio/reshapr-controllers/blob/0.0.3/api/src/main/java/io/reshapr/kubernetes/api/configurationplan/v1alpha1/HeaderPolicy.java) own the behavior described here. --- ## Protect an MCP Endpoint with OAuth 2.0 Use OAuth 2.0 when an MCP endpoint needs authenticated user identity and scopes rather than a shared API key. reShapr validates bearer JWTs at the client-to-proxy boundary and applies the policy to the complete Exposition. :::info Control-plane user login This guide protects MCP endpoints served by the proxy. To authenticate Web UI and CLI users through your organization's identity provider, use **[Connect the Control Plane to an OIDC Provider](../configure-control-plane-oidc.md)**. ::: :::info Client ID Metadata Document compatibility reShapr accepts bearer JWTs issued after an MCP client registers through a [Client ID Metadata Document (CIMD)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-id-metadata-documents). CIMD lets the Authorization Server identify the MCP client from a metadata document hosted at its HTTPS `client_id` URL; the Authorization Server, not CIMD, issues the access token. The proxy does not participate in that client-registration step. It validates the resulting JWT in the same way as any other bearer token: against the configured issuer, JWKS, required claims, expiration, and scopes. ::: ## Prerequisites You need: - the reShapr `1.0.0` CLI, authenticated with `reshapr login`; - an imported Service, its backend endpoint, and a Gateway Group ID; - an OAuth 2.0 test issuer and HTTPS JWKS endpoint reachable by the proxy; - `curl` and `jq`; - RSA- or RSA-PSS-signed test access tokens containing `sub`, `iat`, `exp`, `iss`, and `aud`. Configure the test issuer to mint these tokens with the same trusted signing key: - a valid token containing the required `mcp:invoke` scope; - an expired token; - a token whose `iss` is not in the accepted issuer list; - a token whose `aud` does not identify the Exposition; - a valid token without the required scope. The procedure for creating clients, users, and test tokens is specific to your identity provider. Do not use production tokens for these tests. Set the non-sensitive inputs: ```bash export SERVICE_ID='' export BACKEND_ENDPOINT='https://api.example.com' export GATEWAY_GROUP_ID='' export OAUTH_ISSUER='https://idp.example.com/realms/mcp' export OAUTH_JWKS_URI='https://idp.example.com/realms/mcp/protocol/openid-connect/certs' ``` Read the four test tokens without adding them to shell history: ```bash read -r -s -p 'Valid access token: ' VALID_ACCESS_TOKEN; printf '\n' read -r -s -p 'Expired access token: ' EXPIRED_ACCESS_TOKEN; printf '\n' read -r -s -p 'Wrong-issuer access token: ' WRONG_ISSUER_ACCESS_TOKEN; printf '\n' read -r -s -p 'Wrong-audience access token: ' WRONG_AUDIENCE_ACCESS_TOKEN; printf '\n' read -r -s -p 'Missing-scope access token: ' MISSING_SCOPE_ACCESS_TOKEN; printf '\n' export VALID_ACCESS_TOKEN EXPIRED_ACCESS_TOKEN WRONG_ISSUER_ACCESS_TOKEN WRONG_AUDIENCE_ACCESS_TOKEN MISSING_SCOPE_ACCESS_TOKEN ``` ## Create an OAuth-protected Configuration Plan Create a Configuration Plan that accepts the test issuer, retrieves keys from its JWKS endpoint, and requires `mcp:invoke`: ```bash export RESHAPR_CONFIG_ID="$( reshapr config create-oauth 'oauth-protected' \ --serviceId "${SERVICE_ID}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --oauth2AuthorizationServers "[\"${OAUTH_ISSUER}\"]" \ --oauth2jwksUri "${OAUTH_JWKS_URI}" \ --oauth2Scopes '["mcp:invoke"]' \ --output json \ | jq -er '.id' )" ``` The configured scopes authorize access to the entire Exposition. They do not define different permissions for individual Tools, Prompts, or Resources. ## Create the Exposition Create a named Exposition so that its endpoint remains readable: ```bash export EXPOSITION_ID="$( reshapr expo create \ --configuration "${RESHAPR_CONFIG_ID}" \ --gateway-group "${GATEWAY_GROUP_ID}" \ --name oauth-protected \ --output json \ | jq -er '.id' )" ``` Inspect the Exposition and set the exact named endpoint returned by the CLI: ```bash reshapr expo get "${EXPOSITION_ID}" export MCP_URL='https:///mcp//oauth-protected' export RESOURCE_METADATA_URL='https:///.well-known/oauth-protected-resource/mcp//oauth-protected' ``` ## Inspect Protected Resource Metadata Fetch the metadata published by the Gateway: ```bash curl --fail --silent --show-error "${RESOURCE_METADATA_URL}" \ | jq '{resource, authorization_servers, jwks_uri, scopes_supported}' ``` Verify that: - `resource` equals the MCP endpoint URL; - `authorization_servers` contains `$OAUTH_ISSUER`; - `jwks_uri` equals `$OAUTH_JWKS_URI`; - `scopes_supported` contains `mcp:invoke`. ## Verify endpoint access Use one stateless discovery request for all checks: ```bash export MCP_DISCOVERY_REQUEST='{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"reshapr-oauth-check","version":"0.2.3"},"io.modelcontextprotocol/clientCapabilities":{}}}}' ``` First call the endpoint without a bearer token: ```bash curl --silent --show-error --dump-header - --output /dev/null \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` The response must be `401` and its `WWW-Authenticate` header must contain the `resource_metadata` URL. Repeat the request with the valid token: ```bash curl --fail --silent --show-error \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, text/event-stream' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "Authorization: Bearer ${VALID_ACCESS_TOKEN}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" | jq '.result.supportedVersions' ``` A JSON-RPC result containing `2026-07-28` confirms that the issuer, signature, audience, required claims, expiration, and scope were accepted. By default, the token audience must contain the exact Exposition URL in `$MCP_URL`. Use `--oauth2StaticAudiences '[""]'` on `config create-oauth` only when your Authorization Server issues a stable non-URL audience. ## Verify rejected tokens An expired token must return `401`: ```bash curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "Authorization: Bearer ${EXPIRED_ACCESS_TOKEN}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` A token with an issuer outside the configured list must also return `401`: ```bash curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "Authorization: Bearer ${WRONG_ISSUER_ACCESS_TOKEN}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` A valid token without `mcp:invoke` must return `403`: ```bash curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "Authorization: Bearer ${MISSING_SCOPE_ACCESS_TOKEN}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` A valid token minted for another audience must also return `403`: ```bash curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \ --header 'Content-Type: application/json' \ --header 'MCP-Protocol-Version: 2026-07-28' \ --header 'Mcp-Method: server/discover' \ --header "Authorization: Bearer ${WRONG_AUDIENCE_ACCESS_TOKEN}" \ --data "${MCP_DISCOVERY_REQUEST}" \ "${MCP_URL}" ``` Use Gateway authentication-failure audit events for additional diagnosis when audit and OpenTelemetry export are configured on the Configuration Plan. ## Roll back Delete the Exposition before its Configuration Plan: ```bash reshapr expo delete "${EXPOSITION_ID}" reshapr config delete "${RESHAPR_CONFIG_ID}" unset VALID_ACCESS_TOKEN EXPIRED_ACCESS_TOKEN WRONG_ISSUER_ACCESS_TOKEN WRONG_AUDIENCE_ACCESS_TOKEN MISSING_SCOPE_ACCESS_TOKEN ``` This does not remove clients, users, keys, or test tokens from the identity provider. Revoke or delete those resources there. ## Result The MCP endpoint publishes its OAuth Protected Resource Metadata, accepts a correctly signed, scoped, and audience-bound bearer JWT, rejects expired or unexpected issuers with `401`, and rejects a mismatched audience or missing required scope with `403`. ## Limits - reShapr validates access tokens but does not operate the Authorization Server or its RFC 8414 metadata endpoint. - Release `1.0.0` accepts RSA and RSA-PSS JWT signatures. Symmetric HMAC tokens are rejected. - Audience validation can be disabled with `--oauth2DisableAudienceValidation` for compatibility. This removes the Exposition binding and should not be the production default. - OAuth scopes apply to the Exposition, not to individual Tools, Prompts, or Resources. - Dynamic Client Registration is not provided. ## Next step - **[Test an MCP Endpoint](../test-mcp-endpoint.md)** to list and call Tools with `$VALID_ACCESS_TOKEN`. - **[Security Capabilities and Limits](../../explanations/security-model.md)** explains the controls and non-goals at each trust boundary. - **[Deployment Models and Trust Boundaries](../../explanations/deployment-models-trust-boundaries.md)** covers placement and network responsibilities. --- ## Use OAuth Client Credentials for Backend Calls Use the OAuth 2.0 Client Credentials grant when every call through a Configuration Plan should reach the backend as one machine identity. This flow authenticates the reShapr proxy to the backend; it does not authenticate the MCP client to the Exposition. This guide keeps the OAuth client secret out of control-plane storage by resolving `${env:...}` on the proxy, attaches the resulting Secret to a Configuration Plan, and verifies a backend call. ## Prerequisites You need: - reShapr `1.0.0` and its CLI; - an imported Service and a read-only backend operation; - an OAuth client authorized to call that backend; - a token endpoint that supports `grant_type=client_credentials` and `client_secret_basic`; - control of the target proxy workload environment; - a Gateway Group and a reachable Exposition route; - `curl` and `jq`. Set the non-sensitive inputs: ```bash export SERVICE_ID='' export BACKEND_ENDPOINT='https://api.example.com' export OAUTH_CLIENT_ID='' export OAUTH_TOKEN_ENDPOINT='https://idp.example.com/oauth/token' export OAUTH_SCOPES='backend.read' export GATEWAY_GROUP_ID='' ``` ## Make the client secret available to the proxy Store the OAuth client secret in the workload platform's secret manager and expose it to the proxy as `BACKEND_OAUTH_CLIENT_SECRET`. Do not put its value in a Configuration Plan, command argument, image, or committed manifest. For a Docker proxy, pass the existing environment value without expanding it into the command line: ```bash --env BACKEND_OAUTH_CLIENT_SECRET ``` For Kubernetes, map a Secret key to an environment variable in the proxy container and roll out the workload. The proxy resolves the reference when it needs a token; the control plane stores only the placeholder. ## Create the Client Credentials Secret Create the reShapr Secret and extract its identifier directly: ```bash export BACKEND_SECRET_ID="$( reshapr secret create-client-credentials backend-machine-identity \ --oauth2ClientID "${OAUTH_CLIENT_ID}" \ --oauth2ClientSecret '${env:BACKEND_OAUTH_CLIENT_SECRET}' \ --oauth2TokenEndpoint "${OAUTH_TOKEN_ENDPOINT}" \ --oauth2Scopes "${OAUTH_SCOPES}" \ --description 'Machine identity for the protected backend' \ --output json \ | jq -er '.id' )" ``` The scope option accepts comma-separated or whitespace-separated values. reShapr sends them as one space-separated `scope` parameter; it does not map scopes to individual Tools. Inspect the stored configuration without revealing the runtime value: ```bash reshapr secret get "${BACKEND_SECRET_ID}" --output json \ | jq -e \ --arg endpoint "${OAUTH_TOKEN_ENDPOINT}" \ '.authMethod == "OAUTH2_CLIENT_CREDENTIALS" and .oauth2ClientConfiguration.tokenEndpoint == $endpoint' ``` ## Attach the Secret to a Configuration Plan Create a Plan that uses the machine identity for backend calls: ```bash export CONFIGURATION_PLAN_ID="$( reshapr config create backend-client-credentials \ --serviceId "${SERVICE_ID}" \ --backendEndpoint "${BACKEND_ENDPOINT}" \ --backendSecret "${BACKEND_SECRET_ID}" \ --output json \ | jq -er '.id' )" ``` Protect the MCP endpoint independently with **[an API key](./api-key.md)** or **[OAuth 2.0](./oauth.md)** before exposing it to untrusted clients. Create a named Exposition: ```bash export EXPOSITION_ID="$( reshapr expo create \ --configuration "${CONFIGURATION_PLAN_ID}" \ --gateway-group "${GATEWAY_GROUP_ID}" \ --name backend-client-credentials \ --output json \ | jq -er '.exposition.id' )" reshapr expo get "${EXPOSITION_ID}" ``` ## Verify token acquisition and the backend call Set the returned endpoint and a read-only Tool: ```bash export MCP_URL='https:///mcp//backend-client-credentials' export TOOL_NAME='' export TOOL_ARGUMENTS='{}' ``` Call the Tool as described in **[Test an MCP Endpoint](../test-mcp-endpoint.md)**. A successful backend result confirms that the proxy obtained an access token and sent it as `Authorization: Bearer `. For an isolated verification tenant, inspect the Authorization Server logs and call the same Tool twice before the token expires. The first call should reach the token endpoint; the second should reuse the cached token. Do not log the Basic authorization header, client secret, or access token. The token request has these properties in `1.0.0`: - method `POST` and content type `application/x-www-form-urlencoded`; - body parameter `grant_type=client_credentials`; - optional, space-separated `scope` parameter; - client authentication through HTTP Basic when a client secret is configured; - required JSON response field `access_token`, with optional `expires_in` in seconds. ## Understand token caching The proxy stores the acquired access token in its replicated backend-token cache. The cache key is scoped by strategy and Secret reference, so all calls using that Secret share the machine token across proxy replicas in the same cluster. When the token endpoint returns `expires_in`, the cache lifespan is 15 seconds shorter than that duration, with a minimum of 5 seconds. Without a usable `expires_in`, the lifespan is 300 seconds. After expiry or a cache restart, the next backend call requests a new access token. reShapr ignores refresh tokens for this flow. It always repeats the Client Credentials grant when a new token is needed. ## Rotate the client secret Rotate the credential in this order: 1. create or activate the replacement secret at the Authorization Server; 2. update `BACKEND_OAUTH_CLIENT_SECRET` in the workload secret manager; 3. restart or roll out every proxy replica that consumes it as an environment variable; 4. wait for readiness and registration; 5. invoke the read-only Tool and confirm that the backend accepts the token; 6. revoke the previous OAuth client secret. An access token already in the cache can remain usable until its cache lifespan ends. Plan the overlap at the Authorization Server when immediate revocation would interrupt calls. ## Recover from a failed token exchange Check the proxy logs and the Authorization Server without printing credentials. Common causes are a missing environment variable, invalid client credentials, an unreachable token endpoint, rejected scopes, a non-`200` response, or a response without `access_token`. Fix the owning system, then repeat the Tool call. A failed acquisition is not cached as a valid token. If an obsolete token is still accepted from cache, wait for its bounded lifespan or restart the proxy cluster during a controlled recovery window. ## Result The Configuration Plan uses a proxy-local OAuth client secret to obtain and cache a machine access token. A read-only Tool call reaches the backend with that token, while the MCP endpoint authentication remains an independent policy. ## Limits - Client Credentials represents one shared machine identity, not an MCP user's delegated identity. - reShapr `1.0.0` does not use OAuth refresh tokens for this flow. - Configured scopes apply to the token request, not to per-Tool authorization in reShapr. - The token cache is runtime state, not persistent credential storage; a cluster restart causes a new exchange. - Environment-backed secret rotation requires the workload platform to replace or restart the consuming proxy processes. - `env` is the only provided local-reference scheme in `1.0.0`. ## Next step Use **[Authenticate Backend Calls and Use Elicitation](./backend-auth-and-elicitation.md)** to compare this shared identity with direct credentials and per-user elicitation. Use **[Security Capabilities and Limits](../../explanations/security-model.md)** to review the complete trust boundary. The release-tagged [Client Credentials provider](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/main/java/io/reshapr/proxy/secret/ClientCredentialsTokenProvider.java), [token request implementation](https://github.com/reshaprio/reshapr/blob/1.0.0/commons/src/main/java/io/reshapr/security/OidcUtils.java), and [provider tests](https://github.com/reshaprio/reshapr/blob/1.0.0/proxy/src/test/java/io/reshapr/proxy/secret/ClientCredentialsTokenProviderTest.java) own the behavior described here. --- ## Docs Home # Welcome to reShapr docs Choose the path that matches where you want to run reShapr. Each path starts with an existing environment, states the result you can reach now, and points to the next useful task. ## Choose your path ## Design the right interface --- ## From API Sprawl to Agent Actions
### How reShapr Turns Existing APIs Into Agent-Ready Actions {/* truncate */} Most organizations already have the APIs they need. They have **REST endpoints**, **GraphQL schemas**, **gRPC services**, OpenAPI definitions, internal authentication flows, audit trails, rate limits, and operational habits that took years to mature. The problem is not that these APIs are missing. The problem is that **most APIs were not shaped for agents**. They were designed for developers who understand product boundaries, endpoint naming, error semantics, authentication details, pagination, identifiers, and the right order of calls. An AI agent does not enter the system with that same background. It receives a list of tools, reads their descriptions, chooses one, sends arguments, observes a result, and tries to continue. If we expose every operation from a large API directly as MCP tools, we push **too much responsibility into the model**: - **Too many tools** to choose from. - **Too many parameters** to interpret. - **Too many raw fields** returned to the context window. - **Too many multi-step workflows** left to probabilistic planning. - **Too much sensitive or irrelevant data** crossing the model boundary. At **reShapr**, our position is simple: > The future of API-to-MCP is not bigger tool catalogs. It is **smaller, clearer, governed agent actions**. ### Custom exposure beats raw exposure MCP gives AI clients a common way to call tools, but it does not decide **which tools should exist**. That design decision matters. A backend API operation is often too low-level for an agent. It may expose transport-oriented names, nested request structures, broad schemas, optional relation navigation, or fields that are useful to developers but distracting to a model. reShapr lets you start from existing API artifacts, including **OpenAPI**, **GraphQL**, and **gRPC/Protobuf**, then **reshape the exposed MCP surface** without rewriting the backend service. That is the role of [**Custom Tools**](/docs/references/custom-tools-specification). With a `CustomTools` artifact, you can define a tool that is **smaller, more intentional, and closer to the task** the agent must perform: - Rename a backend operation into a business-friendly tool. - Replace a complex input schema with a focused one. - Fix arguments that should not be chosen by the model. - Bind agent input values into backend arguments. - Expose only the actions that make sense for a given audience or workflow. Instead of asking the agent to understand an entire service, reShapr lets you publish **a curated interface**. > The model should **not need to reverse-engineer your API architecture** to complete a business task. ### From endpoints to business verbs Consider a commerce platform with separate APIs for orders, customers, inventory, shipping, and refunds. A raw exposure might give the agent dozens or hundreds of operations: - `GET /orders/{id}` - `GET /customers/{id}` - `GET /inventory/{sku}` - `POST /refunds` - `GET /shipments/{id}` - `PATCH /orders/{id}/status` Each endpoint may be valid. Each may be well documented. Each may be useful to an application developer. But an agent trying to answer "Can we safely refund this delayed order?" should not have to assemble that workflow from scratch every time. With reShapr, the MCP surface can become: - `get_order_summary(orderId)` - `check_refund_eligibility(orderId)` - `prepare_customer_response(orderId)` - `create_approved_refund(orderId, reason)` Those tools **express intent**. They **narrow the parameter space**. They **reduce ambiguity**. They also give platform teams a stable place to enforce policy. A declarative Custom Tool can map a simple input to a more complex backend request: ```yaml apiVersion: reshapr.io/v1alpha1 kind: CustomTools service: name: Orders API version: '20260707' customTools: get_order_summary: tool: getOrder description: Fetch the fields needed to summarize an order for a support agent. input: type: object properties: orderId: type: string description: The public order identifier. required: - orderId arguments: id: ${orderId} includeCustomer: true includeShipment: true includeLineItems: true ``` The MCP client sees **one focused tool**. The backend still receives the request shape it expects. That is the important distinction: **reShapr adapts the exposure, not the system of record**. ### When one action needs several API calls Some agent tasks are **not a single backend operation**. They require a sequence: 1. Fetch the order. 2. Check shipment status. 3. Verify refund policy. 4. Inspect item inventory or replacement availability. 5. Return a compact decision. Leaving that chain entirely to the model has practical costs. **Intermediate payloads enter the context window**. The model must decide call order. It may retry inconsistently. It may miss a required policy check. It may pass the wrong identifier from one system into another. This is where [**Scripted Custom Tools**](/docs/references/custom-tools-specification#scripted-custom-tools) become especially useful. A scripted Custom Tool lets reShapr expose **one MCP tool** while running a small JavaScript orchestration behind it. The script does not call arbitrary network endpoints. It calls other reShapr tools through the `rs` host API, using **an explicit allow-list**. That matters because the usual reShapr controls still apply to **every underlying call**: - **The script-declared Tool allow-list.** This restricts script calls; it is not per-Tool OAuth authorization. - **Backend secrets and elicitation.** - **Output filtering.** - **Audit and distributed tracing when configured.** - **Gateway execution limits.** - **Cross-service boundaries** inside the same organization. The agent gets **one task-specific tool**. reShapr handles the **controlled API chaining**. ```yaml apiVersion: reshapr.io/v1alpha1 kind: CustomTools service: name: Orders API version: '20260707' customTools: check_refund_eligibility: description: Check whether an order can be refunded and return the reason. input: type: object properties: orderId: type: string required: - orderId tools: - tool: getOrder - service: "Shipping API:20260707" tool: getShipment - service: "Policy API:20260707" tool: evaluateRefundPolicy script: | const order = rs.callTool('getOrder', { id: input.orderId }); if (!order.ok) { rs.fail('Unable to fetch order', { orderId: input.orderId, cause: order.error }); } const shipmentId = order.content.shipmentId; const shipment = rs.callTool('Shipping API:20260707', 'getShipment', { id: shipmentId }); if (!shipment.ok) { rs.fail('Unable to fetch shipment', { shipmentId: shipmentId, cause: shipment.error }); } const policy = rs.callTool('Policy API:20260707', 'evaluateRefundPolicy', { orderStatus: order.content.status, shipmentStatus: shipment.content.status, totalAmount: order.content.totalAmount }); if (!policy.ok) { rs.fail('Unable to evaluate refund policy', { orderId: input.orderId, cause: policy.error }); } return { orderId: input.orderId, eligible: policy.content.eligible, reason: policy.content.reason, nextAction: policy.content.eligible ? 'create_refund' : 'escalate' }; ``` This is not an agent improvising a workflow in the context window. It is an operator-approved tool with a predictable contract. > **Scripted Custom Tools let agents ask for outcomes**, while reShapr keeps the workflow inside governed infrastructure. ### Optional chaining without losing control API chaining is powerful, but it should be **optional and explicit**. Some tools should stay declarative. A focused wrapper around one backend operation is often the cleanest solution. Other tools need orchestration, parallel calls, conditional checks, or cross-service lookups. reShapr supports **both forms** in the same `CustomTools` specification: - Use **`tool` and `arguments`** when one backend operation is enough. - Use **`script` and `tools`** when the business action needs controlled composition. That gives teams a practical migration path. You can begin with **selective exposure**, then introduce scripted tools only where the workflow deserves it. You do not have to choose between raw endpoint exposure and a fully custom MCP server project. reShapr gives you a middle path: **configuration first, scripting where it pays off**. Scripted tools also support asynchronous calls when independent backend calls can run in parallel: ```js const order = rs.callToolAsync('getOrder', { id: input.orderId }); const risk = rs.callToolAsync('Risk API:20260707', 'scoreOrder', { orderId: input.orderId }); const results = rs.awaitPromises([order, risk]); return { order: results[0].ok ? results[0].content : { error: results[0].error }, risk: results[1].ok ? results[1].content : { error: results[1].error } }; ``` The model does not need to juggle those intermediate calls. It receives **the final result** the tool was designed to return. ### Output control is as important as input control **Custom Tools shape what the model can ask for.** [**Tools Output Filtering**](/docs/references/spec-outtools-filtering) shapes what the model receives back. That second half is critical. Even a well-designed tool can return a backend payload with **too many fields**. A customer object may include internal IDs, addresses, risk flags, metadata, nested arrays, pagination cursors, or fields that have no value for the current agent. A GraphQL response can be especially dense. A REST payload can be deeply nested. A gRPC response can still become large once converted to JSON. reShapr applies `ToolsOutputFilters` **after** the backend response has been converted into canonical JSON and **before** the MCP response is returned to the client. For example, the earlier `get_order_summary` tool may receive a rich backend order payload, but the agent only needs the fields required to answer support questions clearly. ```yaml apiVersion: reshapr.io/v1alpha1 kind: ToolsOutputFilters service: name: Orders API version: '20260707' filters: get_order_summary: jsonRetain: - /id - /status - /totalAmount - /customer/name - /shipment/status - /lineItems convertToToon: true ``` This keeps the context window focused on **the order summary**, not on the backend's full data model. ### A governed BFF, or BFA, for agents Traditional applications often use a Backend for Frontend pattern. The frontend should not be forced to understand every internal service, so teams create an API layer shaped for that user experience. A similar idea is emerging for agents. In [**The Back-end for Agents Pattern (BFA)**](https://medium.com/@mdbaraujo/the-back-end-for-agents-pattern-bfa-32e69baf8da3), Michael Douglas Barbosa Araujo describes a mediating layer where agents call stable, task-oriented operations instead of coupling directly to domain APIs, internal schemas, and backend workflows. reShapr makes this pattern concrete for MCP. **Custom Tools** define the agent-facing contract, **Scripted Custom Tools** compose approved backend calls, and **Tools Output Filtering** controls what returns to the model. Agents need the same separation, optimized for **tool choice**, **token budget**, **determinism**, **policy enforcement**, **traceability**, **data minimization**, and **safe cross-service workflows**. reShapr provides that layer for MCP. It lets platform teams translate existing systems into **task-specific tools** while preserving the governance already built around those systems. Agents do not receive raw operational sprawl. They receive **an approved action surface**. That surface can be different for each use case: - A support agent can see refund, shipment, and customer-summary tools. - A finance agent can see reconciliation and invoice tools. - A developer agent can see build, deploy, rollback, and incident tools. - A partner-facing agent can see a stricter filtered subset of the same backend capabilities. **Same services. Different exposures. Different contracts. Different output policies.** > reShapr does **not ask enterprises to replace their APIs**. It lets them expose the right version of those APIs to agents. ### Why this matters in production Prototype MCP servers can tolerate rough edges. Production systems cannot. In production, **every exposed tool becomes part of an operational contract** covering access, credentials, data boundaries, deterministic behavior, observability, and failure handling. reShapr's advantage is that these questions are addressed **at the integration layer**, not left to every agent prompt. **Custom Tools reduce the visible action surface.** **Scripted Custom Tools make multi-call workflows explicit.** **Tools Output Filtering keeps responses small and controlled.** Gateway guard-rails bound script execution with configurable limits such as timeout, maximum tool calls, and maximum scripted nesting depth. Together, these capabilities turn MCP exposure from a raw protocol bridge into an agent-ready integration fabric. ### In summary: expose actions, not sprawl The easiest way to connect an API to MCP is to **expose everything**. It is also the least disciplined. The better path is to **design the MCP surface** with the same care we already apply to public APIs, internal platforms, and production automation. With reShapr: - **Custom Tools** turn backend operations into clear agent actions. - **Scripted Custom Tools** compose approved tools into deterministic workflows. - **Tools Output Filtering** removes irrelevant or sensitive response data before it reaches the model. - **Protocol conversion** lets the same approach work across REST, GraphQL, and gRPC. - **Gateway controls** keep security, secrets, elicitation, audit, and tracing in the infrastructure layer. The result is not just **API-to-MCP conversion**. It is **API-to-agent design**. > Agents work best when they are **given fewer, better tools**. reShapr is how existing APIs become those tools. --- ## From Context Overload to Context Control!
![From context overload to context control — how reShapr makes MCP production-ready at enterprise scale](/img/blog/context-control-hero.jpeg)
Credit: Yacine Kheddache (KubeCon NA Atlanta)
### How reShapr Makes the Model Context Protocol Production-Ready? {/* truncate */} When Anthropic introduced the _Model Context Protocol (MCP)_, it provided developers with something they had been missing: a common language for connecting Large Language Model (LLM) and AI agents to existing systems and services. **But as the MCP ecosystem exploded, so did the** [**_context windows_**](https://medium.com/@pekastel/mcp-and-context-windows-lessons-learned-during-development-590e0b047916)**_!_** Anthropic’s own engineers recently wrote in “[Code execution with MCP: Building more efficient agents](https://www.anthropic.com/engineering/code-execution-with-mcp)” that agents now juggle _hundreds or thousands of tools_, and that every tool definition and intermediate result _consumes tokens,_ slowing agents, inflating costs, and sometimes even breaking workflows. At **reShapr**, we took that exact problem, token consumption and context-window explosion as the _starting point_ of our product design. Before Anthropic formalized the MCP “code-execution” pattern, we asked a more straightforward question: > _What if the protocol wasn’t the issue? What if the_ **_problem was how we expose APIs to it_**_?_ ### We built reShapr on “tokens first” thinking Anthropic’s post illustrates two pain points: 1. Loading all MCP tool definitions upfront floods the model’s context. 2. Intermediate results, even megabytes of JSON or text, must pass back through the model. Both are symptoms of what we call **context coupling**: treating the model’s memory as a data bus instead of an intent space. When we designed the **reShapr’s No-Code MCP Server**, we flipped that pattern. Instead of dumping every API operation and description into the model, _reShapr acts as a programmable filter between your existing API services and the MCP interface._ That design allows you to: - **Selectively translate** only the operations you need into MCP tools. - **Restrict** which tools are visible to which clients, agents, or LLM personas. - **Partition** your exposure by business domain, use case, or security zone. In other words, while the Anthropic team optimized the _client-side execution loop_, reShapr optimized the _server-side surface area_. > We shrink the tool universe **before the model ever sees** it. ### One backend, many MCP faces Here’s where reShapr becomes powerful. From a single backend API service, your CRM, payment gateway, or internal microservice, you can create _multiple MCP servers_, each with: - Its own **name**, **tool set**, and **access policy**. - Its own **domain focus** (e.g., “finance”, “support”, “analytics”). - Optional **context filtering rules** for sensitive data or operations. Need to give a marketing AI access to campaign metrics but not customer PII? Spin up an **mcp-marketing** server exposing only `/metrics` and `/reports`. Need a developer agent to deploy builds? Create **mcp-devops** with `deploy`, `rollback`, and `status` tools. > Same backend, different MCP surface, but **zero code**. This _domain specialization_ not only mirrors real business boundaries; it also **keeps the model’s working set lean**. Why make Claude, ChatGPT, or any other LLM “see” thousands of endpoints if it only needs a few to do what is expected? ### Filtering at both ends Docker’s recent post, “[The Model Context Protocol: Simplifying Building AI apps with Anthropic Claude Desktop and Docker](https://www.docker.com/blog/the-model-context-protocol-simplifying-building-ai-apps-with-anthropic-claude-desktop-and-docker/)_”_, praised Anthropic’s idea of filtering tool exposure for efficiency and safety. We couldn’t agree more, and we’ve implemented that concept from day one. Where Docker focuses on **local developer control** and discovery, a marketplace or container registry of MCP servers, reShapr **operates as an AI integration layer** in all environments (_dev_, _QA_, _prod_). Our filters exist not just for discovery, but for **policy enforcement**. Think of it as _defense in depth for context_: - reShapr filters **which APIs become MCP tools**. - It also filters **which tools each client or agent may call**. - It even filters **which API operations flow**, preventing oversharing. While Docker helps you _run_ MCP servers locally, **reShapr enables you to _govern_ them globally by enabling existing API services for the AI Era**. The two approaches are complementary, not competing, layers of the same emerging ecosystem. ### The context window isn’t infinite, and that’s okay! Anthropic’s own metrics show that replacing direct tool calls with “code execution” reduced token usage by _98.7 %_ in their benchmark scenario. That’s impressive, but it’s also proof of the underlying issue: **agents today overconsume context** because they’re given too much surface to reason about. At reShapr, we believe, that **context discipline beats context expansion**. Instead of asking “how can the model handle more?”, we ask “how can we give it less, but more meaningful input?” Our filtering architecture, domain-specific MCP servers, and selective exposure make it possible to: - Keep **token** budgets **predictable**. - Maintain **shorter prompt-response** cycles. - **Scale** agent ecosystems **horizontally** without blowing up **latency** or **cost**. > You can call it _context-aware architecture_ or, simply, good software engineering. ### Bringing legacy APIs into the AI era One of the most exciting side effects of reShapr’s design is how easily it **modernizes existing APIs**. Take [Stripe’s REST API](https://github.com/stripe/openapi) as an example. Its [OpenAPI](https://raw.githubusercontent.com/stripe/openapi/refs/heads/master/openapi/spec3.yaml) spec is comprehensive and **enormous**. Exposing it directly to an LLM would create a vast array of endpoints, parameters, and schemas, **none of which are optimized for conversational use**. With reShapr, you can wrap Stripe’s API once and expose tailored MCP views: - **mcp-payments** exposing only `create charge`, `refund`, and `list customers`. - **mcp-finops** exposing `reconcile payouts`and `generate statement`. - **mcp-analytics** exposing `top-10 customers`with simplified schemas. Each of these servers can live side-by-side, derived from the same Stripe backend, yet independently optimized for distinct AI personas or business functions. > That’s what we mean when we say **“AI-native by translation, not reinvention.”** ### No-Code setup, enterprise-grade results reShapr’s configuration model is intentionally simple: 1. Connect to your existing [**Rest API**](https://swagger.io/specification/), [**GraphQL**](https://graphql.org/), or [**gRPC**](https://grpc.io/) service. 2. Use our interface to select the operations you want to expose. 3. Add optional field-level filters, renames, or descriptions. 4. Publish, and you instantly have a fully compliant MCP server or workflow of MCP servers. Behind the scenes, reShapr automatically: - **Generates** the MCP schema and metadata. - **Handles** auth propagation and error mapping. - **Enforces** your security and throttling policies. - **Provides** live observability dashboards for every tool call. What Anthropic’s engineers call “progressive disclosure”, which allows models to discover only the tools they need, occurs _naturally_ in reShapr through **configuration**, rather than **custom code**. ### Scaling AI with less friction, not more code Anthropic’s “code execution” pattern is brilliant for power users and developer-friendly agents that can write their own scripts. But in enterprise environments, most teams don’t want every agent writing arbitrary code in a sandbox. They want **predictability**, **governance**, and **measurable efficiency**. reShapr delivers that by externalizing code execution into _controlled MCP endpoints_ rather than letting each agent spin its custom code. You receive all the benefits, including reduced token usage, efficient data handling, and composable workflows, without the operational risk associated with distributed sandboxes. And when you do want to give agents controlled compute, reShapr integrates **seamlessly** with your **existing infrastructure**: Kubernetes jobs, serverless functions, or dedicated compute pools all behind the same **No-Code MCP Server** powered by reShapr. ### Complementing, not competing, with the ecosystem Let’s be clear: the work from Anthropic, Docker, and MCP / API Gateway vendors is moving the entire industry forward. Each focuses on a different layer: > We stand on their shoulders and **build the bridge between** prototype and **production**. reShapr is what **makes the MCP ecosystem _deployable at scale_**. ### Toward a governed, efficient AI integration fabric As MCP adoption accelerates, organizations face the same maturity curve APIs did a decade ago: 1. **Experimentation** — local prototypes and SDKs. 2. **Expansion** — hundreds of endpoints and tools. 3. **Governance** — access control, observability, cost management. 4. **Optimization** — token efficiency, domain specialization, and security. reShapr sits squarely at stages _2_, _3_ and _4_. We help teams operationalize MCP servers safely, efficiently, and at scale, **without requiring them to rewrite their systems or manually code integrations**. ### In summary: Control the context, don’t expand it Anthropic’s _Code Execution with MCP_ article is an important milestone. It demonstrates how agents can be more intelligent about tool usage by converting direct calls into code. reShapr builds on that same insight, but applies it to the _entire lifecycle_ of MCP deployment. - We **anticipated** the token and context explosion problem. - We **engineered** a system that filters, segments, and secures MCP exposure. - We **deliver** remote-first, production-ready servers, not non-realistic experiments. - We **complement** Anthropic and others, including Docker, API, and MCP Gateway vendors, by bringing enterprise rigor to the protocol. In AI integrations, as in good software design, **less context is often more powerful**. > Ultimately, the goal isn’t to make **LLMs** see more; it’s to **make them see _better_**. --- ## reShapr vs Official GitHub MCP 🥊
### 31 Tool Calls Become 1 Predictable Agent Action {/* truncate */} This benchmark answers a practical agent question and turns the concepts introduced in our previous posts, [From Context Overload to Context Control](https://reshapr.io/blog/from-context-overload-to-context-control) and [From API Sprawl to Agent Actions](https://reshapr.io/blog/from-api-sprawl-to-agent-actions), into a **real-world measurement**. By providing a **fair comparison** between the **official GitHub MCP tools** and **reShapr**, it shows how task-shaped MCP actions translate into **measurable performance gains**. > If an AI agent needs **recent pull request velocity** for `microsoft/vscode`, the public GitHub repository for Visual Studio Code, should it drive the official GitHub MCP pull-request tools directly, or should it call **one purpose-built reShapr MCP action**? ## The Use Case The test is **intentionally narrow and reproducible**. The agent starts with a simple goal: understand recent pull request velocity in `microsoft/vscode`. To answer it properly, it needs the **10 most recently updated pull requests**, the details for each pull request, **up to 5 review touchpoints**, and **up to 10 changed file paths** per pull request. That is a realistic workflow for **release tracking**, **engineering management**, **review triage**, and **agentic codebase analysis**. It is also exactly the kind of workflow where **generic tools create agent overhead**: list the pull requests, then loop over each pull request to fetch details, reviews, and files. The official GitHub MCP path exposes useful **primitive tools**. reShapr turns the same API surface into **one higher-level MCP action**. This matters beyond speed. reShapr makes repeated agent workflows **predictable, reliable, and reproducible**. The model can still reason and decide, but the workflow boundary becomes stable: **one named action, one input schema, one output shape, one measurable execution path**. ## The Result This is the latest **measured live run** against `microsoft/vscode`, using the **same 10 pull requests** for the official MCP and reShapr custom-action comparison:
Then reShapr applies a **second, independent optimization** at the **output boundary**. The same custom action was called through two `ToolsOutputFilters` artifacts on a matched reShapr run: first with **JSON retain/patch rules**, then with the same filter plus **`convertToToon: true`**. The official GitHub MCP baseline does not expose an equivalent output-filter artifact in this test, so the comparison stays explicit: **official primitive workflow versus reShapr custom action**, then **reShapr custom action versus reShapr custom action with output filtering**.
That second optimization is smaller than the **31-to-1 action design**, but it still matters: another **12.4% response-byte reduction** from the custom action to filtered TOON output, on top of the main **97.2% reduction** versus the official GitHub MCP loop. The key point is **fairness**: reShapr does not pretend GitHub has less data. The reShapr action still performs the **source GitHub operations** needed to produce the answer. The win is that the agent sees **one semantic tool call, one compact response, and one stable task-shaped contract**. ## Methodology The most natural way to run this use case is through **ChatGPT or another MCP-capable conversational agent**. A user would normally ask for the **outcome**, not for the individual API calls: ```text Using the available GitHub MCP tools, analyze recent pull request velocity for the public repository microsoft/vscode. Use the 10 most recently updated pull requests. For each pull request, collect the pull request details, up to 5 review touchpoints, and up to 10 changed file paths. Return a compact summary with pull request id, title, creator, state, creation and merge timestamps, review touchpoints, and changed paths. ``` That is the typical agent experience: **one natural-language request becomes a multi-step MCP tool plan**. The model has to decide how to list pull requests, fan out over the selected pull requests, and gather the details, reviews, and changed files before it can synthesize an answer. With reShapr, the conversational agent can call **one task-shaped action**: ```json { "name": "get_repo_velocity_metrics", "arguments": { "owner": "microsoft", "name": "vscode", "prCount": 10, "reviewCount": 5, "fileCount": 10 } } ``` For the benchmark, we did not want the measurement to depend on **model behavior**. Different models, prompts, retry policies, or agent runtimes can make slightly different planning choices. One run may call tools in a different order. Another may inspect extra fields, retry a tool call, or stop early after a transient error. So the benchmark separates the **user experience** from the **measurement method**. The LLM prompt describes the real agent task. The **curl-based benchmark** measures the underlying MCP workflow directly. It sends the **same MCP requests over Streamable HTTP**, records each HTTP roundtrip, captures each `tools/call` response body, and calculates **latency, bytes, and estimated token load** from the collected responses. This gives us a **technical, predictable, and reproducible** way to measure the benchmark without letting **model behavior distort the numbers**. The expected LLM-agent result should be directionally the same: the official GitHub MCP path needs a **multi-call loop**, while the reShapr path exposes **one task-shaped action**. The curl benchmark makes that difference **explicit and auditable**. ## Why This Benchmark Agents are bad at **unnecessary loops**. Every extra tool call adds protocol overhead, latency, logs, error states, retry decisions, and context the model may need to read. Pull request velocity is a clean benchmark because the naive workflow naturally becomes **`1 + 3N` tool calls**: one call to list pull requests, then three follow-up calls for every pull request to fetch details, reviews, and changed files. For 10 pull requests, this is: ```text 1 list call + (10 PRs * 3 follow-up calls) = 31 agent-facing MCP tool calls ``` With reShapr, the agent calls: ```text get_repo_velocity_metrics(owner: "microsoft", name: "vscode", prCount: 10, reviewCount: 5, fileCount: 10) ``` The action returns **only what the use case needs**: pull request id, title, creator, state, creation and merge timestamps, review touchpoints, and changed paths. That is the demonstration: not "can MCP call GitHub?", but **"can we shape an API into the tool the agent actually needed?"** ## The Bigger Issue: Predictable Agents LLM-driven agents are powerful because they can adapt, but that flexibility becomes a liability when the **same business workflow is rediscovered on every run**. One run may call tools in a different order. Another may skip a follow-up call. Another may over-fetch data, hit a transient error, or spend context on fields the task never needed. reShapr does not make the model deterministic. It makes the **agent workflow boundary predictable**. With the primitive-tool path, the agent is responsible for planning and executing the full loop: ```text list pull requests -> for each pull request -> get details -> get reviews -> get files -> shape the result ``` With reShapr, that loop becomes a reusable MCP action: ```text get_repo_velocity_metrics ``` The operational benefits are concrete. The **tool-call count** becomes predictable, **latency and payload size** become measurable, the **output shape** stays stable, and **failures** are localized to one action. Most importantly, **the same workflow** can be reproduced by humans, tests, and agents. This is a big deal for agent adoption. Teams do not only need agents that can improvise. They need agent capabilities that can be **audited, benchmarked, documented, and run again with confidence**. ## What Is Being Compared Both paths use **Streamable HTTP MCP**, not stdio. The **official GitHub MCP path** uses the remote endpoint `https://api.githubcopilot.com/mcp/x/pull_requests/readonly` with the `X-MCP-Toolsets: pull_requests` header. The agent receives useful primitive tools, mainly `list_pull_requests` and `pull_request_read`, but it must still run the **full loop itself**: list pull requests, then fetch details, reviews, and files for each pull request. The **reShapr path** starts from the same **GitHub source of truth**. It imports the **full official GitHub REST OpenAPI YAML file** by URL, exposes GitHub REST operations through reShapr MCP, attaches **one custom tool** that orchestrates the pull request workflow, and can attach `ToolsOutputFilters` artifacts to **trim or TOON-encode** the custom action output. The result is a **compact task-specific payload** rather than a pile of primitive tool responses. Official GitHub OpenAPI URL used by reShapr: ```text https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml ``` The OpenAPI file is **not copied into this article**. reShapr imports it directly from that URL. ## How reShapr Shapes the Workflow The key reShapr artifact is a small `CustomTools` definition. It tells reShapr which GitHub REST operations belong to the task and exposes the workflow as **one agent-facing action**: ```yaml apiVersion: reshapr.io/v1alpha1 kind: CustomTools service: name: GitHub v3 REST API version: "1.1.4" customTools: get_repo_velocity_metrics: description: Fetch recent pull requests, review touchpoints, and changed file paths as one compact repository velocity action. tools: - tool: get_repos_owner_repo_pulls - tool: get_repos_owner_repo_pulls_pull_number - tool: get_repos_owner_repo_pulls_pull_number_reviews - tool: get_repos_owner_repo_pulls_pull_number_files ``` Those **four primitive operations** are the calls a generic agent has to **discover, sequence, and repeat** when it drives GitHub MCP directly. reShapr keeps the source API operations **visible and measurable**, but moves the orchestration behind **one predictable action**: `get_repo_velocity_metrics`. That is the practical difference between exposing an **API** to an agent and exposing a **capability** to an agent. ## Reproduce It Yourself The **full benchmark package** is available in the reShapr demos repository: ```text https://github.com/reshaprio/reshapr-demos/tree/main/benchs/github-mcp-vs-reshapr ``` It includes the **README**, **scripts**, **reShapr artifacts**, **output filters**, **payload examples**, and **curl-only benchmark runner** used to collect the numbers in this article. Use it to run the benchmark against `microsoft/vscode`, inspect **every MCP request and response body**, and adapt the workflow to another repository or MCP server. The package keeps the setup **reproducible** while keeping this post focused on the story: **31 primitive tool calls become one predictable action**, then **output filtering and TOON** reduce the payload further. We welcome **feedback, comments, and contributions**. If you reproduce the benchmark on another repository, improve the harness, or want to compare another MCP server, contributions are welcome. ## How to Read the Numbers The benchmark separates **two things**: 1. MCP protocol roundtrips: every Streamable HTTP exchange, including initialization and `tools/list`. 2. Agent tool calls: only `tools/call` requests, because those are the calls an agent has to plan, issue, observe, and recover from. (*) For bytes and token estimate, the figures count **only tool-call response bodies**. That is the content the agent must consume to continue its work. The token estimate is intentionally simple: **response bytes divided by four**. The script uses `(bytes + 3) / 4` to round that byte-based estimate up to the nearest whole token. It is **not a tokenizer-specific claim**, but it is a useful size proxy. For the output-filter comparison, make sure the three reShapr variants inspect the **same pull request IDs**. `microsoft/vscode` is active enough that the "recently updated" set can change between runs. The published output-filter chart uses **one matched run** where all reShapr variants inspected the same 10 pull requests: ```text 325138,325045,325180,325168,325173,325170,325175,322952,325163,325165 ``` The reShapr payload includes this field: ```json { "source_backend_calls": 31 } ``` That field is important because it proves reShapr is **not hiding the source work**. It is consolidating the workflow at the **MCP boundary** and shaping the result **before the agent sees it**. ## Why reShapr Wins Here The official GitHub MCP path gives the agent general-purpose pull-request tools. That is useful, but the agent still has to plan and drive the workflow step by step. reShapr lets an API owner or platform team expose a use-case-level action: ```text get_repo_velocity_metrics ``` That action is better for agents because it has **one clear intent**, **one compact input schema**, **one response shape**, and **one failure surface**. It also gives the API owner one place to encode pagination, fan-out, filtering, and field selection, instead of asking every agent to rediscover that orchestration pattern at runtime. For platform teams, this is the practical reShapr advantage: it turns **existing APIs into agent-native tools** without asking every agent to rediscover the same orchestration loop. For this use case, the difference is not subtle. **31 agent tool calls become 1**. **34 MCP HTTP roundtrips become 4**. **267.8 KB** of official MCP tool responses becomes **7.4 KB**, and roughly **67.0K estimated tokens** becomes **1.9K**. When the use case can safely drop fields, **output filters and TOON** can reduce the already compact custom output further. That is the kind of improvement that changes **how reliable an agent workflow feels in practice**. ## What This Demonstrates This benchmark does not argue that primitive MCP tools are bad. They are necessary. It shows that primitive tools are often **not the right final interface for agents**. The best agent-facing interface is usually **not the raw API**. It is a **domain action shaped around the task**: ```text Repository velocity, not pull request plumbing. ``` reShapr provides the **missing layer**. It can import the **official API description**, expose it as **MCP over Streamable HTTP**, compose existing operations into a **custom tool**, reduce the payload to the **fields the task needs**, and keep the full flow **reproducible**. In this benchmark, that is why the reShapr approach wins: it keeps the **GitHub source of truth**, removes **avoidable agent overhead**, and turns a fragile multi-step agent loop into a **predictable workflow that can be reproduced, measured, and trusted**. --- ## Six Use Cases for Accelerating AI with reShapr 🚀 {/* markdownlint-disable MD001 MD026 MD030 MD045 */} If you’re aiming to build AI‑native applications faster without sacrificing security or overhauling existing infrastructure, [**reShapr**](https://reshapr.io/) offers a transformative edge. Here are six compelling use cases showcasing how organizations leverage reShapr to streamline API enablement, accelerate prototyping, and securely integrate AI into production.
![Six use cases for accelerating AI with reShapr — from API enablement to secure hybrid deployment](/img/blog/six-use-cases-hero.jpg)
Credit: [Osarugue Igbinoba](https://unsplash.com/fr/photos/couloir-en-arc-eclaire-xfbn4nNiS3s)
{/* truncate */} ### 1. 🛠️ Convert Internal APIs into MCPs — AI Enablement in Minutes **Challenge:** Developers want internal APIs to be AI‑accessible without rewriting backends. **How reShapr helps:** - With reShapr, you can instantly expose existing REST, gRPC, or GraphQL APIs as Model Context Protocol (MCP) endpoints, requiring no code. - That means teams can prototype AI assistants or agents in minutes, rather than waiting for a backend redesign to occur. - Built on a No‑Code MCP Server with broad protocol compatibility, reShapr supports OpenAPI and gRPC out of the box. ### 2. 🧪 Build Fast, Safe AI POCs — MVPs Using Live Data **Challenge:** How can you safely build an AI proof‑of‑concept using production data? **Solution with reShapr:** - Enables developers to connect AI prototypes directly to live APIs while enforcing policies and safeguards. - Data access remains secure and auditable, reducing risk and accelerating feedback loops. - Ideal for organizations testing use cases early before scaling to production. ### 3. 🔁 Sync API and MCP Lifecycles — Prevent Contract Drift **Challenge:** API changes cause agent behavior to break. **reShapr’s approach:** - Integrates into CI/CD pipelines to automatically regenerate MCP schemas when APIs evolve. - Enforces version checks and alignment, minimizing manual maintenance and eliminating drift between endpoints and AI agents. ### 4. 🌍 Multi‑Protocol Support — REST, gRPC, GraphQL? All Covered **Challenge:** Modern stacks often span multiple API protocols. **reShapr’s capability:** - Natively supports REST, gRPC, and GraphQL, turning any service into MCP tools for LLM consumption. - No extra translation layers or adapters required, ensuring performance and consistency across services. ### 5. 🧱 Secure Cloud, Hybrid, or On‑Prem **Challenge:** Sensitive systems must remain inside secure perimeters. **reShapr’s flexibility:** - Supports fully on‑premises deployments or hybrid models where compute can reside in your private cloud. - Keeps data and logic inside firewalls while still enabling AI connectivity, making it a suitable fit for regulated sectors and enterprises. ### 6. 📡 Gate External AI Agents — Safely Expose APIs Without Re‑architecting **Challenge:** Expose internal services to AI agents without rebuilding everything. **reShapr’s solution:** - Acts as a controlled MCP Server, enabling selective exposure of internal APIs. - Provides runtime controls, logging, and schema enforcement, so backends remain untouched but safely accessible. ### 🔍 Real World Example: Open Meteo REST → MCP with reShapr reShapr has been demonstrated to instantly translate the [Open-Meteo](https://open-meteo.com/) REST weather API into an MCP endpoint without requiring any code. This highlights how quickly external data sources can become AI‑ready tools using reShapr’s solution. First, authenticate with the reShapr online try: ```shell ❯ reshapr login -s https://try.reshapr.io ℹ️ Opening browser: https://try.reshapr.io/cli/login?redirect_uri=http://localhost:5556 ℹ️ Listening for authentication callback on http://localhost:5556 ✅ Login successful! ℹ️ Welcome, yada! ℹ️ Organization: yada ✅ Configuration saved to /Users/yacine/.reshapr/config ``` Then, import and expose the API as an MCP server using in a single command: ```shell ❯ reshapr import -u https://raw.githubusercontent.com/open-meteo/open-meteo/refs/heads/main/openapi/forecast.yml --backendEndpoint https://api.open-meteo.com ✅ Import successful! ℹ️ Discovered Service Open-Meteo APIs with ID: 0PXEW1ZDWFCZS ✅ Exposition done! ✅ Exposition is now active! Exposition ID : 0PXEW2272H0PB Organization : yada Created on : 2026-03-28T19:06:26.743899 Service ID : 0PXEW1ZDWFCZS Service Name : Open-Meteo APIs Service Version: 1.0 Service Type : REST -> https://api.open-meteo.com Endpoints : mcp.try.reshapr.io/mcp/yada/Open-Meteo+APIs/1.0 ``` That's it. Your MCP server is live at `https://mcp.try.reshapr.io/mcp/yada/Open-Meteo+APIs/1.0`, use `https` for reShapr Try, or `http` depending on your deployment. Connect it to any MCP client, LLM conversational agent, or agentic workflow as a remote HTTP Streamable MCP server and enjoy 😎 More demos available on our [YouTube channel](https://www.youtube.com/@reShapr) 🙌 ### ✅ Why reShapr Matters Whether you’re embedding AI assistants, building RAG workflows, or enabling LLM-driven agents, reShapr lets you move forward quickly **without compromising control**: - Instant MCP Server for internal or external APIs, securely exposed. - Safe AI experimentation on real data for faster iteration. - Seamless version control and lifecycle sync. - Protocol-agnostic support with no vendor lock-in. - Flexible deployment to match enterprise security policies. ### 🎯 Business Benefits Summarized #### Speed - Prototype AI integrations in minutes using production APIs with no backend overhaul #### Security & Compliance - Enforce governance, logging, and access controls even in sensitive environments #### Flexibility - Works across REST/GraphQL/gRPC, deploys Cloud, hybrid, or on-prem #### Cost Efficiency - Avoid rewriting backend services, minimize development overhead ### 🔗 Looking Ahead Integration into a modern AI ecosystem is as straightforward as flipping a switch, and your existing APIs become first-class AI-ready tools. > reShapr empowers businesses to **accelerate AI use cases**, **reduce integration friction**, and **deliver secure production-ready experiences while effectively leveraging existing infrastructure**. To explore reShapr’s vision and strategic context, see _"_[_Why reShapr_](/blog/why-reshapr)_"_ for deeper insight into the MCP framework and its architecture. --- ## Welcome to reShapr ⭐️ {/* markdownlint-disable MD001 MD026 MD030 MD045 */}
![Welcome to reShapr — Turn your API AI-Native](/img/blog/welcome-to-reshapr.jpg)
A few months ago, we started experimenting with a new idea through a project called **[Micepe](https://www.youtube.com/@Micepe)**. The idea was simple but powerful: What if existing APIs could instantly become MCP tools usable by AI agents without writing glue code? {/* truncate */} ## Collect feedback and validate our ideas We launched a private beta program with a handful of enterprises, and the response was beyond what we expected. The teams involved consistently told us the same thing: > This solves a **real problem for AI-native** architectures. ### The feedback we received
The first results from our reShapr evaluation at AXA are very promising. It addresses complex enterprise challenges with rare clarity and a true open source heart. As a maintainer in the CNCF space, I’m excited to support this community effort and see how it transforms how we expose services to agents.
[Sebastien Degodez](https://www.linkedin.com/in/sebastien-degodez/) Software Engineer | TechLead
When organizations have hundreds or even thousands of APIs to maintain, speed and security become non-negotiable. reShapr transforms how existing services are exposed to agents, delivering an unmatched FinOps ratio and the fastest path to production. It’s a definitive solution for scaling agentic context within highly regulated environments.
[Ludovic Pourrat](https://www.linkedin.com/in/ludovic-pourrat/) API Architect | Platform Architect
I’m excited by reShapr’s clear purpose and the co-founders’ vision, which directly address the real-world challenges I see across the organizations I work with. Open-sourcing such a critical component to advance MCP democratization is a meaningful step forward for the ecosystem, and I’m genuinely looking forward to being part of this journey and helping build a strong, open community around reShapr.
[Sébastien Blanc](https://www.linkedin.com/in/s%C3%A9bastien-blanc-08a73b1/) Java Champion | Developer Relations The discussions with platform teams and developers, and the **concrete use cases** we saw convinced us of one thing: - This should **not remain** a proprietary experiment. **It deserves to become an open project that the community can shape and build together.** ## Micepe is **evolving** into → reShapr So today, we are taking the next step, **reShapr** is a new open source project created by the co-founders and maintainers of **[Microcks](https://microcks.io/)**, the **[CNCF](https://landscape.cncf.io/?selected=microcks&item=app-definition-and-development--application-definition-image-build--microcks)** open source project used by organizations such as **BNP Paribas**, **J.B. Hunt**, **Amadeus**, **Michelin**, **GSMA** and **[many more](https://github.com/microcks/.github/blob/main/ADOPTERS.md)** to improve API and cloud native application development. With reShapr, our goal is to help organizations: - **Turn existing APIs** into AI-native tools **instantly** - **Expose services** as MCP servers for **AI agents** - **Accelerate** the adoption of **AI + cloud native** architectures The success of our private beta and the **amazing feedback from the enterprises involved** reinforced our conviction that the project should grow within an **open** ecosystem. We intend to donate the project to the **[AAIF](https://aaif.io/)** open source **foundation**, so it can evolve with community open governance, transparency, and long-term sustainability. ## This is just the beginning of the journey. If you followed Micepe, the story continues with **reShapr**. And if you are interested in **AI-native APIs**, **MCP** tools, and **developer platforms** for **AI agents**, we would love to have you involved in the project, the **open source** way. > **reShapr** is now **live** and **open** for the **community**. Join the **AI-Agent Revolution**. We are **building the future** of developer platforms for AI agents, the **open source** way. Whether it’s a **[GitHub ⭐️](https://github.com/reshaprio/reshapr)**, a bug report, or a pull request, we’d love to have you involved. ### Here’s how you can get involved: 🚀 **Check** it out: **[Try reShapr online](/docs/tutorials/try-reshapr-online)** 🤝 **Join** the project: On **[Discord](https://discord.gg/KyDUdam34h)**. **Help us** shape the future of AI-native APIs on **[GitHub](https://github.com/reshaprio/reshapr)** 📱 **Follow** us: Stay connected on **[LinkedIn](https://www.linkedin.com/company/reshapr/)**, **[Bluesky](https://bsky.app/profile/reshapr.io)**, **[X](https://x.com/reshaprio)** and **[YouTube](https://www.youtube.com/@reShaprio)** for the latest updates. --- ## Why reShapr?(Blog) {/* markdownlint-disable MD001 MD026 MD030 MD045 */} ## **Why Enterprises Shouldn’t Build MCP Servers from Scratch, and What to Do Instead** The AI-native era is here. Whether you’re integrating copilots into internal workflows, building LLM-powered agents for customer service, or exposing organizational knowledge to a retrieval-augmented generation (RAG) stack, the foundation is the same: (your) APIs. > But there’s a **major roadblock** that nearly every **enterprise** hits. {/* truncate */} ## The Problem: (Your) Services Weren’t Designed for LLMs Enterprises are under growing pressure to make internal and business services accessible to Large Language Models ([LLMs](https://www.ibm.com/think/topics/large-language-models)). But those services were not built for generative agents that parse documentation, generate prompts, or request human-like reasoning. Suddenly, platform teams are being asked: - “**How can** we let our AI assistant fetch data from our CRM, ERP, or ticketing systems?” - “**Can we** integrate our knowledge base into our chatbot by giving it access to our API?” - “**Is it safe** to let external copilots interact with internal endpoints?” The typical answer is to build a Model Context Protocol ([MCP](https://modelcontextprotocol.io/introduction)) server, a new service and development interface layer that AI models can use. > That’s where the **real** trouble begins. ## Why Building Your Own MCP Server Is a Trap A Model Context Protocol (MCP) server acts as the bridge between Large Language Models (LLMs) and your services. At first glance, building one may seem straightforward. But very quickly, most teams realize: - **It’s more than OpenAPI**: You need translation layers, prompt guards, retries, rate limiting, and grounding logic. - **Security gets messy fast**: How do you avoid exposing credentials in LLM prompts? How do you enforce identity, scope, and input validation? - **Fragility creeps in**: Most DIY solutions end up as brittle pipelines of JSON transforms, hardcoded logic, and embedded hacks that fall apart under load or worse, leak data. This approach often duplicates your API logic into a parallel, standalone MCP layer, creating unnecessary complexity, increased maintenance overhead, and misalignment between your API and its AI-facing interface. > A smarter approach is to extend your **existing API infrastructure** rather than reinventing it. ## Introducing reShapr: Your Zero-Code AI Translation Layer reShapr exists to solve this exact problem. Rather than building an MCP server from scratch, you can use reShapr to **instantly translate your existing APIs (REST, gRPC, GraphQL) into AI-native endpoints.** - **Zero** code - **No** rewrites - **No** custom Agents - **No** vendor lock-in > Think of it as a **plug-and-play** interface between your infrastructure and the AI-native world. ## Time-to-Market Matters: How reShapr Accelerates AI Deployment ### Let’s compare two paths to AI-native readiness With reShapr, your team can: - Deploy an AI-native interface **in minutes, not** weeks/months. - Avoid rewriting APIs or **duplicating business logic**. - Launch pilots **quickly** and iterate **without platform bottlenecks**. - Get to **proof of value before your competitors** even clear their architecture review.
![Comparison of DIY MCP server development taking weeks versus reShapr zero-code deployment in minutes](/img/blog/why-reshapr-inline.png)
Allocation icons created by kmg design — [Flaticon](https://www.flaticon.com/free-icons/allocation).
> Speed isn’t just convenience; it’s a **competitive** advantage. ### Secure by Design: Leverage What You Already Trust When it comes to security, we don’t believe in reinventing the wheel. reShapr’s philosophy is simple: **build on top of the infrastructure, controls, and credentials you already trust.** ### Here’s how: - **Reuse Existing Policies**: Identity, rate limiting, scopes, and audit logging are inherited from your infrastructure (API gateway). There is no need to rebuild backend authentication or create shadow permission systems. - **OAuth2 support**: Optionally leverage the latest MCP protocol enhancements to enable secure additional authentication at the reShapr layer. - **Multi-Protocol Support**: Whether your backend speaks REST, gRPC, or GraphQL, reShapr understands it, without flattening security models or introducing fragile transforms. - **Leverage trusted infrastructure**: reShapr translates your existing API operations into MCP tools, making all your current security layers and validations automatically apply to LLM-generated requests transparently and without modification. You simply reuse the same production-hardened controls you already trust. > Security isn’t an afterthought; it’s a **prerequisite for exposing APIs** to any AI agent. reShapr gives you the controls **without** the complexity. 👉 **Read** "[Security options & Secrets](/docs/explanations/security-model)" ### Developer Experience That Doesn’t Fight You We know what it’s like to be asked to ship an AI feature “by the end of the quarter.” That’s why reShapr was built with a developer-first mindset: - **One command to deploy**: Configure your MCP interface with a simple Rest API, CLI or UI. - **Declarative Config**: Specify how each API should be exposed, if at all and define which operations are permitted or excluded. - **Infrastructure Agnostic**: Works with any cloud, on-premise deployment, or hybrid environment. - **Portable and Auditable**: No hidden agents. No black boxes. You stay in control. > It’s not magic; it’s just good engineering, abstracted for real-world use. ### Built by People Who’ve Been There The reShapr team has designed API gateways, built security systems, and operated large-scale cloud infrastructure. We’ve seen the fragile SDK generators, the spaghetti YAML, and the LLM prompt hell, and we knew there had to be a better way. > So we built one! ## Why This Matters Now Enterprises are racing to make their data and systems usable with AI-native tools. But each delay in integration leads to lost market share, stalled innovation, and missed opportunities. The organizations that win will not be the ones that spend months writing custom LLM plugins. They will be the ones that expose their APIs **safely, scalably, and instantly.** > reShapr makes that possible with **zero code** and **full** control. ## TL;DR: Why Choose reShapr 🤔 - ✅ **Fastest path to AI-native APIs**, from concept to production in a day. - ✅ **No rewrites**, works with your existing REST, gRPC, or GraphQL APIs. - ✅ **Security-first**, leverages your existing infrastructure and shields against prompt ingestion risks. - ✅ **Highly portable**, no vendor lock-in: deploy anywhere. - ✅ **Enterprise-ready**, built for scale, extensibility, and real-world complexity. ## Ready to Try reShapr? If you’re exploring how to safely expose your APIs to LLMs, whether for internal copilots, customer-facing agents, or RAG backends, **reShapr is the fastest and most secure way to do it.** > 1. Use the [**Try it Online**](https://try.reshapr.io) option to explore the platform. Follow our detailed ["Try reShapr online"](https://reshapr.io/docs/tutorials/try-reshapr-online). > 2. Or Run the platform locally using containers, check out the [“Run using Docker Compose”](https://reshapr.io/docs/how-to-guides/docker-compose/). **Make your APIs usable by AI without rewriting a single line of code.**