mcp-ovh-api
An MCP server for the OVHcloud API focused on Object Storage (buckets, objects, project users, S3 credentials, storage policies) — read-only by default, with mutating tools registered only when OVH_ALLOW_WRITES=1.
Diagnose configuration: the provided schema registers only
ovh_auth_status, which reports the live auth method, endpoint, default project/region, whether writes are enabled, and what to set when tools are missing — its presence alone means the rest of the toolset is not (yet) exposed.Inspect (default read-only set):
ovh_whoami, list/get projects, regions, and bucket details; list/get objects and object versions; list project users and S3 credentials; read lifecycle configs and storage policies; preview a policy without applying it.Move bytes without proxying them:
ovh_presign_objectmints a time-limited presigned S3 URL (GET only while writes are off).Escape hatch:
ovh_requestcalls any/1.0path, GET-only unless writes are enabled.With writes enabled: create/update buckets and lifecycles, copy/delete objects (incl. bulk and versioned deletes), create project users and S3 credentials, reveal S3 secrets, apply storage policies and grant bucket access.
Safe provisioning:
ovh_provision_s3_usercreates a new (non-owner) project user and applies awrite-only/read-only/read-writepreset policy — including the explicitDenyneeded to stop an upload-only key reading back its own objects — then mints credentials; destructive tools requireconfirm: true.Keep secrets out of the transcript: any secret-minting tool accepts
secretFileto write the secret to a0600file (optionally confined viaOVH_SECRET_DIR) instead of returning it in the conversation.
Provides tools for interacting with the OVHcloud API, focused on Object Storage: managing buckets, objects, project users, S3 credentials, and storage policies, with read-only operations by default and optional write tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-ovh-apiList my OVHcloud object storage buckets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@mgcrea/mcp-ovh
A Model Context Protocol server for the OVHcloud API, focused on Object Storage: buckets, objects, project users, S3 credentials and the storage policies that tie them together.
The server is read-only by default. Mutating tools are not merely refused when writes are off — they are never registered, so an agent cannot call them at all.
Features
Curated tools over OVHcloud's
/1.0API with descriptions that spell out its traps (see Traps worth knowing).Read-only by default.
OVH_ALLOW_WRITES=1adds the write tools; the destructive ones then additionally require an explicitconfirm: trueon every call.All three OVH auth methods, picked automatically from whichever env vars are present: OAuth2 service account (recommended), application key + consumer key (SHA1-signed, with automatic clock-drift correction), or a static access token.
Policy presets — including
write-only, which OVH's own role shortcut does not offer.List results are summarized, and OVH's deprecated per-bucket
objects[]array (which embeds every object in the bucket) is suppressed on both ends.X-Ovh-QueryIDis surfaced on every error, because that is the first thing OVH support asks for.A
secretFileparameter on every tool that mints or reveals a secret, so the secret can go to a0600file instead of the conversation (see Keeping secrets out of the transcript).An
ovh_requestescape hatch for the rest of the API (GET-only unless writes are enabled).Native
fetch, no runtime dependencies beyond the MCP SDK and Zod.
Related MCP server: saveformedearai
Install
pnpm install
pnpm buildConfigure
Pick one auth method.
(A) OAuth2 service account — recommended
Create an IAM service account at https://www.ovh.com/manager/#/iam/service-account.
Attach an IAM policy granting it your public cloud project (for object storage:
publicCloudProject:apiovh:*on the project resource).Copy the client id and secret into
.env.
Tokens last an hour and are cached and refreshed ahead of expiry.
(B) Application key + consumer key
Create the triplet in one shot at https://eu.api.ovh.com/createToken/. The access rules you list there are fixed forever — a consumer key cannot be widened afterwards, so grant what you need up front:
GET /cloud/project/*
POST /cloud/project/*
PUT /cloud/project/*
DELETE /cloud/project/*
GET /meRequests are SHA1-signed over secret+consumerKey+METHOD+URL+BODY+TIMESTAMP. A clock more
than ~30s off OVH's fails every call with a misleading Invalid signature, so the server
probes /auth/time once at startup and corrects for the delta.
(C) Static access token
Set OVH_ACCESS_TOKEN and it is sent as Authorization: Bearer.
cp .env.example .envVariable | Required | Description |
| no |
|
| (A) | IAM service account. Their presence selects OAuth2. |
| (B) | Application key pair. |
| (B) | Consumer key issued alongside them. |
| (C) | Pre-minted bearer token. |
| no | Force |
| no | Default project — the 32-char hex |
| no | Default storage region, upper-case ( |
| no | Set to |
| no | Confine every |
| no | Override the API base URL entirely. |
| no | Retry budget for 401 / 429 / 5xx. Defaults to |
| no | Refresh the OAuth2 token this long before expiry. Defaults to |
| no | Set to |
Run
pnpm start # speaks JSON-RPC over stdioWire into Claude Code
Add to .mcp.json (project) or ~/.claude.json (global):
{
"mcpServers": {
"ovh": {
"command": "node",
"args": ["/absolute/path/to/mcp-ovh/dist/cli.js"],
"env": {
"OVH_CLIENT_ID": "...",
"OVH_CLIENT_SECRET": "...",
"OVH_CLOUD_PROJECT": "abcdef0123456789abcdef0123456789",
"OVH_REGION": "UK"
}
}
}
}Inspect the tools
npx @modelcontextprotocol/inspector node dist/cli.jsTraps worth knowing
All are baked into the tool descriptions, but they explain the shape of this server:
OVH has no bucket policies — only user policies. One raw JSON document per project user, and that document is the entire access-control surface. Setting a policy replaces everything that user could previously do, across all buckets.
A policy cannot restrict the bucket's owner. OVH falls back to ACLs and the owner holds
FULL_CONTROL: "if the user is the bucket owner and even if there is no explicit allow in the policy file, the user will be authorized." A restricted key must therefore belong to a new project user that did not create the bucket.ovh_provision_s3_userchecks the bucket'sownerIdand refuses when you point it at the owner.The same fallback applies per object. Whoever uploads an object owns it and gets
FULL_CONTROLon it through the object ACL. So merely omittings3:GetObjectdoes not stop an upload-only key from reading back everything it wrote — verified against the live API, where a bare allow-list policy happily served the key its own uploads while correctly denying every object someone else had uploaded. An explicitDenyis required, and it does beat the ACL. That is why thewrite-onlypreset ships aDenystatement rather than a bare allow-list.
Two smaller ones. s3:PutObject alone still permits blind overwrite of existing keys
inside the allowed prefix — a "write-only" key is not an append-only key, which is a good
reason to enable versioning on the bucket. And policy changes take up to ~30 seconds to
propagate: a probe run five seconds after ovh_set_storage_policy still shows the old
behaviour, which reads exactly like a policy that silently failed.
Tools
Every project-scoped tool takes an optional project, and every storage tool an optional
region, overriding OVH_CLOUD_PROJECT / OVH_REGION per call. Tools marked W exist
only when OVH_ALLOW_WRITES=1; those marked ⚠️ are destructive and additionally require
confirm: true.
Start with ovh_whoami. It reports which auth method is live, which account you are, and
the clock delta against OVH — which is what a 401 on the signature method is nearly always
about.
Area | Tools |
Meta |
|
Buckets |
|
Objects |
|
Users & keys |
|
Policies |
|
Escape hatch |
|
ovh_presign_object is the only way bytes move: the server never proxies object content, it
mints a time-limited presigned S3 URL instead. With writes off it signs GET only.
Policy presets
ovh_preview_policy, ovh_set_storage_policy and ovh_provision_s3_user share three
presets, all scopable to a key prefix:
Preset | Grants |
| Allow |
|
|
| both, plus |
OVH's built-in roles (admin, deny, readOnly, readWrite, via ovh_grant_bucket_access)
have no write-only equivalent — that is why the raw-policy path exists. The multipart pair is
included deliberately: every S3 SDK auto-switches to multipart above ~8-16MB, and without
abort/list a failed upload orphans parts the key holder cannot clean up and keeps paying for.
OVH validates policy actions against a fixed enum and rejects the whole document with a
400 if one is unknown — s3:GetObjectVersion and s3:DeleteObjectVersion exist in AWS but
not there. The presets use only accepted actions, and a test pins that.
Handing out a write-only upload key
The motivating case: an app embeds an S3 key in a shipped binary, so the key must be able to upload and nothing else, while the read/write key stays with the developer.
ovh_get_bucket bucket=dev-rgis-ar → note ownerId
ovh_preview_policy bucket=dev-rgis-ar preset=write-only prefix=uploads/
ovh_provision_s3_user bucket=dev-rgis-ar preset=write-only prefix=uploads/ \
description=ar-app-uploader confirm=trueThat creates a new project user (never the bucket owner), applies the policy, and only then
mints credentials — a key that exists before its policy is a key that briefly had whatever the
default allows. The secret is returned once — add secretFile=/abs/path.json to have it
written to a 0600 file instead of the conversation.
Verify against the real S3 API before handing it over — a policy that reads correctly can still be shadowed by ownership, and wait ~30s after setting it or you will be probing the previous policy:
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...
# An array, not a string: zsh does not word-split an unquoted $var, so the
# `S3='aws ...'` form you would write in bash silently becomes "command not found".
S3=(aws --endpoint-url https://s3.uk.io.cloud.ovh.net --region uk s3api)
"${S3[@]}" put-object --bucket dev-rgis-ar --key uploads/probe.txt --body /dev/null # 200
"${S3[@]}" get-object --bucket dev-rgis-ar --key uploads/probe.txt /dev/null # 403
"${S3[@]}" list-objects-v2 --bucket dev-rgis-ar # 403
"${S3[@]}" delete-object --bucket dev-rgis-ar --key uploads/probe.txt # 403
"${S3[@]}" put-object --bucket dev-rgis-ar --key elsewhere/probe.txt --body /dev/null # 403The get-object line is the one that matters: it is the check that catches trap 3, and it
passes only because of the preset's Deny.
Keeping secrets out of the transcript
Four tools return a live secret: ovh_create_s3_credentials and ovh_reveal_s3_secret (an S3
secret), ovh_provision_s3_user (an S3 access key and secret) and ovh_create_project_user
(an OpenStack password). By default it comes back in the tool result, which means it lands in
the assistant's transcript, and from there in whatever history the client keeps.
Pass secretFile to any of the four and the secret goes to that file instead. The result then
carries only the non-secret fields (user id, username, access key, endpoint), the path, and a
note that the secret is not in it.
ovh_provision_s3_user bucket=dev-rgis-ar preset=write-only prefix=uploads/ \
secretFile=/Users/me/.secrets/ar-uploader.json confirm=true{
"access": "...",
"secret": "...",
"userId": 9001,
"username": "user-...",
"bucket": "dev-rgis-ar",
"region": "uk",
"endpoint": "https://s3.uk.io.cloud.ovh.net"
}The S3 tools write access, secret and userId (provisioning adds the fields above);
ovh_create_project_user writes username, password and userId.
The path must be absolute and its directory must already exist — nothing is created for you but the file.
The file is created exclusively with mode
0600: an existing file, or a symlink in its place, is refused and never overwritten.The path is checked before the OVH call, so a bad path does not mint a credential that then has nowhere to go. If the write still fails afterwards, the error names what was minted (access key, user id — never the secret) so you can revoke it.
With
OVH_SECRET_DIRset, the file must resolve inside that directory. Its parent is resolved throughrealpath, so a symlink inside the directory that points elsewhere is refused. Unset, any absolute path is accepted.
Without secretFile the tools behave exactly as before.
Develop
pnpm dev # tsdown --watch
pnpm test # vitest
pnpm typecheck
pnpm lint
pnpm formatLicense
MIT
Available Tools
1 toolovh_auth_statusOVHcloud: Auth StatusARead-only
Report whether this server has working OVHcloud credentials, which auth method and endpoint it uses, the default project and region, whether writes are enabled, the OVH_SECRET_DIR that secretFile paths are confined to, and — when something is missing — exactly what to set. Call this first when a tool you expected is not listed: an absent tool here means missing configuration, not a bug.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say readOnlyHint=true; the description adds real substance on top: the full set of reported facts, the confinement of `secretFile` paths to OVH_SECRET_DIR, the write-enabled flag, and prescriptive remediation ("exactly what to set") when something is missing. That is behavior an agent could not infer from the annotation alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, with the diagnostic use case front-loaded after the enumeration. The first sentence is dense with comma-separated items but every item is a distinct reported fact that earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return-value burden — and it does so by enumerating every reported field plus the remediation behavior for missing config. For a zero-parameter, read-only diagnostic tool this is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is no parameter semantics to explain; baseline applies. The description correctly spends none of its length on inputs and instead covers what the no-arg call reports.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Uses a specific verb ("report") with a precisely enumerated payload: credential validity, auth method, endpoint, default project/region, write-enablement, and the OVH_SECRET_DIR constraint. An agent knows exactly what this tool returns and why it exists without consulting other fields.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger — "Call this first when a tool you expected is not listed" — plus the interpretation rule that an absent tool means missing configuration, not a bug. It stops short of stating when *not* to call it (e.g., routine polling), so it lands just under a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
ovh_auth_status
TDQS
Scored across 1 tool
There is only one tool, so there is no possibility of confusing it with another. Its purpose is clearly stated as an authentication/configuration diagnostic.
The single tool name `ovh_auth_status` follows a clear snake_case domain-action pattern. With only one tool, there is no inconsistency to assess.
The server claims to be an OVHcloud API server but exposes only a single diagnostic tool. This is an extreme mismatch between the broad implied domain and the one available operation.
The surface contains only an auth-status check and no OVHcloud resource operations such as listing projects, managing instances, or any CRUD lifecycle. It is effectively a diagnostic stub rather than a usable API toolset.
Maintenance
Related MCP Connectors
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Read-only MCP server for AIStatusDashboard status, incidents, metrics, and fallback recommendations.
MCP server for Product Management
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server for AWS S3 — list buckets, browse objects, upload/download files, and generate presigned URLs.7128 npm4MIT
- AlicenseAqualityDmaintenanceMCP server for uploading, listing, and retrieving files on S3-compatible storage (AWS S3, DigitalOcean Spaces) with public/private access and temporary URLs.40MIT
- FlicenseNot gradedqualityDmaintenanceRead-only MCP server for browsing and reading S3 objects, with tools for listing buckets/objects, reading text and binary files, and extracting text from PDFs.-
- AlicenseAqualityAmaintenanceEnables management of Scaleway IAM (applications, API keys, policies, permission sets) and Object Storage bucket policies through an MCP server.11711 npmMIT