mcp-ovh-api
MCP server for OVHcloud Object Storage, enabling management of buckets, objects, project users, S3 credentials, and storage policies. Read-only by default with opt-in write tools.
README
@mgcrea/mcp-ovh-api
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.- 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.
Install
pnpm install
pnpm build
Configure
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 /me
Requests 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 .env
| Variable | Required | Description |
|---|---|---|
OVH_ENDPOINT |
no | ovh-eu (default), ovh-ca, ovh-us, kimsufi-*, soyoustart-*. |
OVH_CLIENT_ID / OVH_CLIENT_SECRET |
(A) | IAM service account. Their presence selects OAuth2. |
OVH_APPLICATION_KEY / _SECRET |
(B) | Application key pair. |
OVH_CONSUMER_KEY |
(B) | Consumer key issued alongside them. |
OVH_ACCESS_TOKEN |
(C) | Pre-minted bearer token. |
OVH_AUTH_METHOD |
no | Force oauth2, signature or accessToken. Otherwise inferred. |
OVH_CLOUD_PROJECT |
no | Default project — the 32-char hex serviceName, not the display name. |
OVH_REGION |
no | Default storage region, upper-case (GRA, SBG, DE, UK). |
OVH_ALLOW_WRITES |
no | Set to 1 to register the write tools. Off by default. |
OVH_API_URL |
no | Override the API base URL entirely. |
OVH_MAX_RETRIES |
no | Retry budget for 401 / 429 / 5xx. Defaults to 3. |
OVH_REFRESH_SKEW_SECONDS |
no | Refresh the OAuth2 token this long before expiry. Defaults to 60. |
OVH_DEBUG |
no | Set to 1 to log debug output to stderr. |
Run
pnpm start # speaks JSON-RPC over stdio
Wire into Claude Code
Add to .mcp.json (project) or ~/.claude.json (global):
{
"mcpServers": {
"ovh": {
"command": "node",
"args": ["/absolute/path/to/mcp-ovh-api/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.js
Traps 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 | ovh_whoami, ovh_list_projects, ovh_get_project, ovh_list_regions, ovh_get_region |
| Buckets | ovh_list_buckets, ovh_get_bucket, ovh_get_bucket_lifecycle · W ovh_create_bucket, ovh_update_bucket, ovh_set_bucket_lifecycle, ⚠️ ovh_delete_bucket_lifecycle, ⚠️ ovh_delete_bucket |
| Objects | ovh_list_objects, ovh_get_object, ovh_list_object_versions, ovh_presign_object · W ovh_copy_object, ⚠️ ovh_delete_object, ⚠️ ovh_delete_object_version, ⚠️ ovh_bulk_delete_objects |
| Users & keys | ovh_list_project_users, ovh_get_project_user, ovh_list_s3_credentials · W ovh_create_project_user, ovh_create_s3_credentials, ovh_reveal_s3_secret, ⚠️ ovh_delete_s3_credentials, ⚠️ ovh_delete_project_user |
| Policies | ovh_get_storage_policy, ovh_preview_policy · W ⚠️ ovh_set_storage_policy, ⚠️ ovh_grant_bucket_access, ⚠️ ovh_provision_s3_user |
| Escape hatch | ovh_request — any /1.0 path, GET-only unless writes are enabled |
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 |
|---|---|
write-only |
Allow s3:PutObject, s3:AbortMultipartUpload, s3:ListMultipartUploadParts on the prefix — plus an explicit Deny on s3:GetObject / s3:GetObjectAcl bucket-wide |
read-only |
s3:ListBucket + s3:GetBucketLocation on the bucket, s3:GetObject on the objects |
read-write |
both, plus s3:DeleteObject |
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=true
That 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.
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 # 403
The 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.
Develop
pnpm dev # tsdown --watch
pnpm test # vitest
pnpm typecheck
pnpm lint
pnpm format
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。