Manifold
Allows MCP clients to interact with OpenAPI/Swagger-compliant REST APIs by automatically generating MCP tools from OpenAPI 3.x / Swagger 2.x specifications.
Click on "Install 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., "@ManifoldList all tools from the OpenAPI backend"
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.
Manifold
One interface. Many connections. Manifold.
English | 日本語
Manifold is a gateway that acts as an MCP server while connecting to multiple external MCP servers and OpenAPI / Swagger-compliant REST APIs on the backend.
Why "Manifold"?
The name Manifold comes from an engine's intake manifold.
An intake manifold is the component that distributes air and fuel evenly and efficiently from a single inlet to multiple cylinders. We named this project Manifold because its structure is similar.
Engine manifold | This project |
Single inlet | Requests from MCP clients |
Distribution / routing | Protocol conversion / routing |
To multiple cylinders | To multiple external MCP / REST APIs |
Related MCP server: MCP Context Forge Gateway
Architecture
MCP Client
│
▼
┌─────────────┐
│ Manifold │ ← this server
└─────────────┘
│ │
▼ ▼
External OpenAPI / Swagger
MCP REST API Server
ServerFeatures
OpenAPI / Swagger → MCP conversion: Automatically generates MCP tools from OpenAPI 3.x / Swagger 2.x specifications
MCP backend aggregation: Transparent reverse proxy to external MCP servers
Built-in OAuth 2.1 server: Authorization server with PKCE (S256) support
Pluggable backend authentication: Choose one of static header (
authValue) / OAuth 2.0 (oauth2) / API key Token Exchange (tokenExchange)Resource links: Stores binary content from tool responses in S3 and returns download URLs (resource links)
Lazy connection (stdio) / stateless connection (http): stdio backends connect on first request (no backend dependency at gateway startup); http backends open a fresh connection per request and never share a session across callers
Selectable storage: Session / token management backed by Redis or SQLite
OpenTelemetry support: OTLP export of traces, metrics, and logs (metrics also support Prometheus-style pull)
Requirements
Go 1.26+
Redis or SQLite (for session management)
Installation
Download binary
Download the latest binary from Releases.
Build from source
git clone https://github.com/nonchan7720/manifold.git
cd manifold
go build -o manifold .Docker
docker pull ghcr.io/nonchan7720/manifold:latestUsage
Start the gateway
# Run the binary
manifold gateway
# Specify a config file explicitly (-c / --config, config name without extension)
manifold gateway -c config
# Run from source
go run main.go gateway
# Docker (working directory is /home/nonroot)
docker run -p 9999:9999 \
-v $(pwd)/config.yaml:/home/nonroot/config.yaml \
ghcr.io/nonchan7720/manifold:latestDocker Compose (development)
Starts a development environment including Redis.
docker compose up -dReady-to-run configuration examples are available in the examples/ directory.
Configuration
Place a configuration file (config.yaml) in the current directory or in a config/ subdirectory.
Configuration values support environment variable expansion in the form ${VAR} or ${VAR:-default}.
Connecting to an MCP backend
Expose an external MCP server through Manifold.
gateway:
port: 9999
# openssl rand -base64 32
encryptKey: ${ENCRYPT_KEY}
mcpServers:
my-mcp-server:
description: External MCP server
transport: http
url: http://localhost:8080/mcp
sqlite:
path: ./tmp/manifold.dbConnecting to an OpenAPI / Swagger backend
Automatically generate MCP tools from an OpenAPI specification.
gateway:
port: 9999
encryptKey: ${ENCRYPT_KEY}
mcpServers:
my-api:
description: Sample REST API
spec: https://example.com/api/openapi.json
baseURL: https://example.comOpenAPI backend with OAuth 2.0 authentication
gateway:
port: 9999
encryptKey: ${ENCRYPT_KEY}
mcpServers:
my-api:
description: OAuth-protected API
spec: https://example.com/api/openapi.json
baseURL: https://example.com
oauth2:
clientID: YOUR_CLIENT_ID
clientSecret: YOUR_CLIENT_SECRET
authURL: https://example.com/oauth/authorize
tokenURL: https://example.com/oauth/token
scopes:
- read
- write
redis:
addrs:
- "${REDIS_ADDRS:-localhost:6379}"
db: ${REDIS_DB:-0}Configuration reference
gateway
Field | Type | Description |
| int | Listening port (default: 8081) |
| string | TLS private key file path (optional) |
| string | TLS certificate file path (optional) |
| string | Token encryption key (required). Base64-encoded 32-byte AES-256 key. Generate with |
| duration | Interval for re-fetching OpenAPI mode specs (e.g. |
gateway.specRefresh
Periodically re-fetches the specs of OpenAPI mode servers (mcpServers.<name>.spec) and updates the MCP tool definitions without restarting Manifold. Added tools are registered, removed tools are unregistered, and connected clients are notified via notifications/tools/list_changed.
gateway:
specRefresh:
interval: 5mChanges are detected by hashing the fetched spec document, so a change made only in an externally $ref-ed document leaves the hash unchanged and is not picked up. When a fetch or parse fails, the existing tool definitions are kept and the next interval retries.
mcpServers.<name>
Server names (<name>) are used in URL paths, so only alphanumerics, _, and - are allowed.
Field | Type | Description |
| string | Server description (required; included in |
| string | Transport for MCP backends ( |
| string | Endpoint for the HTTP transport |
| string | Command for the stdio transport |
| []string | Arguments for the stdio command |
| map[string]string | Environment variables for the stdio process |
| string | Path or URL of an OpenAPI/Swagger specification |
| string | API base URL in OpenAPI mode (required when |
| map[string]string | Extra headers added to API requests |
| object | Static authentication settings ( |
| object | OAuth 2.0 settings (see below) |
| object | Token Exchange settings (see below) |
| duration | Per-server override of |
authValue / oauth2 / tokenExchange are mutually exclusive; only one may be configured at a time.
mcpServers.<name>.oauth2
Field | Type | Description |
| string | Client ID (required) |
| string | Client secret (required) |
| string | Authorization endpoint (required; absolute URL) |
| string | Token endpoint (required; absolute URL) |
| []string | Scopes to request |
mcpServers.<name>.tokenExchange
Exchanges the API key received from the client for an OAuth token at the specified token exchange endpoint, and uses it for backend requests. Exchange results are cached, and rate limits (429) are respected.
Field | Type | Description |
| string | Absolute URL of the token exchange endpoint (required) |
redis
Field | Type | Description |
| string | Redis URL (e.g. |
| []string | List of host:port pairs (for Cluster/Sentinel) |
| string | Username |
| string | Password |
| int | Database number |
| string | Sentinel master name |
| bool | Enable TLS |
| bool | Enable Cluster mode |
sqlite
Field | Type | Description |
| string | Database file path ( |
Either redis or sqlite must be configured.
storage
Stores content included in OpenAPI/Swagger tool responses (images, binaries, etc.) in external storage and returns resource links (download URLs). When unset, no storage is used.
Field | Type | Description |
| string | Storage type. Currently only |
| string | Host for download URLs (when set, content is served via Manifold's |
| string | S3 bucket name (required when |
| string | S3 object key prefix (required when |
storage:
type: s3
hostURL: https://manifold.example.com
s3:
bucket: my-bucket
keyPrefix: manifold/mediafileFetch
When a URL is passed to a file input field of an OpenAPI/Swagger tool, Manifold downloads the file from that URL. As an SSRF countermeasure, connections to private/loopback/link-local IPs and the http:// scheme are rejected by default.
Field | Type | Description |
| bool | Allow connections to private/loopback IPs and |
| []string | Allowlist of hosts (hostname, or |
| int64 | Maximum bytes for downloaded/base64/text content. 0 or unset defaults to 524288000 (500 MiB) |
Each field can also be overridden via environment variables (FILEFETCH_MAXSIZE, FILEFETCH_ALLOWLOCAL, FILEFETCH_ALLOWEDHOSTS).
fileFetch:
allowLocal: false
maxSize: 524288000 # 500MiB
# allowedHosts:
# - example.com
# - files.example.com:8443telemetry
Output settings for traces, metrics, and logs via OpenTelemetry.
Field | Type | Description |
| string | Service name |
| string | Environment name ( |
| bool | Gzip compression for OTLP export |
| object | Trace settings ( |
| object | Metrics settings ( |
| object | Log settings ( |
For the http / grpc exporters, specify addr (host:port) or url, plus an optional headers map of extra request headers (e.g. for a SaaS OTLP endpoint that requires an Authorization header). grpc also accepts insecure. With metrics.exporterType: pull, Prometheus-format metrics are exposed at the /metrics endpoint instead of OTLP push.
headers can also be supplied as a single environment variable holding a JSON object, instead of a nested YAML map — useful when the value (e.g. a bearer token) is injected at deploy time rather than checked into config.yaml:
telemetry:
trace:
http:
url: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}
headers: ${OTEL_EXPORTER_OTLP_HEADERS_JSON}export OTEL_EXPORTER_OTLP_HEADERS_JSON='{"Authorization":"Basic xxxxx"}'telemetry:
serviceName: manifold
trace:
enabled: true
grpc:
addr: localhost:4317
insecure: true
metrics:
enabled: true
exporterType: push
grpc:
addr: localhost:4317
insecure: true
logs:
enabled: true
grpc:
addr: localhost:4317
insecure: trueTool authorization (OPA sidecar)
Manifold can enforce which server/tool pairs a caller may use on tools/call and tools/list, delegating each decision to an external OPA sidecar. Disabled by default (authz.enabled: false, preserving prior behavior); authentication, group resolution, and policy storage stay out of Manifold's scope — it trusts identity headers injected by an upstream layer and queries OPA for the decision.
authz:
enabled: true
opaURL: http://localhost:8181
timeout: 3s
decisionPath:
list: /v1/data/mcp/authz/allowed_tools
call: /v1/data/mcp/authz/allow
catalog: /v1/data/mcp/authz/allow_catalog
headers:
userID: x-user-id
userGroups: x-user-groups
input:
user: user
groups: groups
server: server
tool: tool
tools: tools
toolName: name
fromHeaders:
tenant:
header: x-tenant-id
required: trueField | Type | Default | Description |
| bool |
| Enables the authz middleware. Every other field below is only read when |
| string |
| Base URL of the OPA sidecar ( |
| duration |
| Per-decision HTTP timeout |
| string |
| OPA data path queried once per |
| string |
| OPA data path queried once per |
| string |
| OPA data path queried once per |
| string |
| Inbound header carrying the caller's user ID |
| string |
| Inbound header carrying the caller's groups, comma-separated |
| string |
| Inbound header that, set to the exact string |
| string |
| JSON key for the caller's user ID in every decision input |
| string |
| JSON key for the caller's groups in every decision input |
| string |
| JSON key for the server name in the |
| string |
| JSON key for the tool name in the |
| string |
| JSON key for the tool array in the |
| string |
| JSON key for the tool name in each |
| map[string]object |
| Maps a decision-input field name to the inbound HTTP header it is read from. Empty by default, adding nothing. See "Multi-tenant policy data" below |
| string | — | Inbound header carrying the field's value. Required, and must be a valid HTTP header field name |
| bool |
| When |
| string |
| How the raw header value becomes a JSON value: |
Manifold treats the headers.userID value as an opaque string: it doesn't interpret it, just passes it through as-is to the key authz.input.user names in the decision input (default user). In a multi-tenant deployment, use a format that includes the tenant (e.g. {tenant}:{user}) so policies can tell tenants apart — or use input.fromHeaders instead (see "Multi-tenant policy data" below), in which case headers.userID doesn't need to carry the tenant. headers.userGroups values should likewise be immutable opaque IDs (e.g. ULIDs) rather than display names, since display names can change.
input lets a policy author match an existing decision-input contract instead of renaming their policy to Manifold's defaults. Keys that appear together in the same input object must be pairwise distinct: user / groups / server / tool (the tools/call input), user / groups / tools (the tools/list input), and server / toolName (each tools/list array element) — startup validation rejects a collision within any of those groups. Every key must also be non-empty. input.fromHeaders field names must likewise be non-empty and must not collide with any of the (possibly renamed) top-level keys above — user / groups / server / tool / tools. The comparison is case-sensitive, since OPA input keys are: with the defaults in place, a field named User is accepted because input.user is a different key. toolName is not reserved: it only names a key inside the tools array elements, never a top-level one. The same header may be assigned to more than one field.
Prerequisites
Manifold trusts headers.userID / headers.userGroups — and, if configured, headers.bypass and every header named in input.fromHeaders — on every request without verifying them itself, the same caveat as the WebMCP reverse gateway's forwardAuth mode (see its Trust boundary section in docs/design/webmcp-reverse-gateway.md). Before enabling authz.enabled:
The fronting proxy must strip or overwrite any client-supplied headers of the same names, so a caller cannot forge its own identity
Direct access to Manifold bypassing that proxy must be blocked at the network layer (e.g. a Kubernetes
NetworkPolicy)headers.bypassis more sensitive than the identity headers: a caller that can set it totruedisables authorization entirely for its own requests, regardless of identity or group membership. The fronting proxy must strip or overwrite it with the same rigor, and every network path that can reach Manifold without going through that proxy must be closed at the network layer — not merely authenticated separately
Decision contract
Manifold POSTs {"input": ...} to opaURL + decisionPath.call for every tools/call, to opaURL + decisionPath.list once per tools/list (batched across every tool, not queried per tool), and to opaURL + decisionPath.catalog for every GET /mcp/list?tools=true. The examples below use the default authz.input key names; every key is renameable (see the input table above):
// tools/call
{"input": {"user": "user-042", "groups": ["team-finance"], "server": "billing-svc", "tool": "create_invoice"}}
// → {"result": true}
// tools/list
{"input": {"user": "user-042", "groups": ["team-finance"], "tools": [{"server": "billing-svc", "name": "create_invoice"}, ...]}}
// → {"result": [{"server": "billing-svc", "name": "create_invoice"}, ...]}
// GET /mcp/list?tools=true
{"input": {"user": "user-042", "groups": ["team-finance"]}}
// → {"result": true}Manifold does not prescribe a shape for OPA's data document; policies are free to structure it however they like — see examples/opa/ for a working policy.rego and data.json (data.policies[<group id>].tools as a list of <server>/<tool> glob patterns, data.policies[<group id>].catalog as a boolean).
Multi-tenant policy data
input.fromHeaders maps a decision-input field name to an inbound HTTP header, so a value the upstream identity layer already knows (a tenant ID, a region) reaches the policy without being encoded into headers.userID. Every configured field is resolved for every decision kind (tools/call, tools/list, and GET /mcp/list?tools=true) and added as a top-level field alongside user / groups / etc.:
authz:
input:
fromHeaders:
tenant:
header: x-tenant-id
required: true
roles:
header: x-roles
required: false
type: list
seat_count:
header: x-seat-count
type: number// tools/call
{"input": {"user": "user-042", "groups": ["team-finance"], "server": "billing-svc", "tool": "create_invoice", "tenant": "acme", "roles": ["admin", "auditor"], "seat_count": 42}}type controls the JSON type the raw header value becomes:
| Decision input value | Notes |
| The raw header value, unmodified | |
| An array of strings | Split on |
| A JSON number | The raw digits are sent through unrounded. A value that isn't a number denies the request, whether the field is required or not |
required defaults to true — omitting the key keeps the fail-closed behavior of the identity headers. With required: false, a missing or empty header (or a list with no non-blank element) leaves the field out of the decision input entirely rather than sending an empty value, so a policy should guard it:
# input.roles is absent on requests that carried no x-roles header, so read
# it through a default instead of indexing it directly.
roles := object.get(input, "roles", [])That tenant field lets data be organized per tenant instead of flat, so one bundle can serve every tenant without a naming convention baked into user:
package mcp.authz
default allow := false
allow if {
tenant_policies := data.tenants[input.tenant].policies
some group in input.groups
some pattern in tenant_policies[group].tools
glob.match(pattern, ["/"], sprintf("%s/%s", [input.server, input.tool]))
}This replaces the {tenant}:{user} convention described above for headers.userID — with input.fromHeaders resolving the tenant explicitly, headers.userID only needs to identify the user within that tenant.
Distributing per-tenant data
Manifold only knows opaURL and decisionPath.*; how policy and data reach the sidecar is OPA's concern (see "Operating recommendations" below for serving them as a bundle over HTTP). Once data is keyed by tenant, you can choose how finely to split it:
flowchart LR
M[Manifold] -->|"POST /v1/data/mcp/authz/allow<br/>input.tenant = acme"| O[OPA sidecar]
O -.->|poll| B[(bundle service)]
B -.->|"mcp-authz/policy.tar.gz<br/>roots: mcp/authz"| O
B -.->|"tenants/acme/bundle.tar.gz<br/>roots: tenants/acme"| O
B -.->|"tenants/globex/bundle.tar.gz<br/>roots: tenants/globex"| OOne OPA can load several bundles, each owning a disjoint subtree of data, so a tenant's policy data can be published and rolled back independently of every other tenant's. The OPA side of that looks like:
services:
bundles:
url: https://bundles.example.com
bundles:
policy:
service: bundles
resource: mcp-authz/policy.tar.gz
tenant-acme:
service: bundles
resource: tenants/acme/bundle.tar.gz
tenant-globex:
service: bundles
resource: tenants/globex/bundle.tar.gzEach bundle's .manifest declares the subtree it owns; the Rego above keeps reading data.tenants[input.tenant] unchanged.
// mcp-authz/policy.tar.gz
{"revision": "2026-08-29-01", "roots": ["mcp/authz"]}
// tenants/acme/bundle.tar.gz
{"revision": "2026-08-29-01", "roots": ["tenants/acme"]}Three constraints follow from how OPA merges bundles:
Roots must not overlap. OPA refuses to activate a bundle whose root conflicts with another's (
["tenants"]alongside["tenants/acme"], for example), so splitting means splitting every tenant, and shared data cannot live in the same subtree as tenant-specific dataSplitting is not isolation. Every bundle still lands in the one
datatree of the one OPA process, so a policy that readsdata.tenants.globexcan. The tenant boundary is enforced by the policy indexing throughinput.tenant; bundle boundaries only scope updates and blast radiusAdding a tenant is an OPA config change.
bundles:is static, so each new tenant needs the sidecar reconfigured. OPA's discovery feature can distribute the bundle list itself, at the cost of another moving part, and every bundle polls independently, so very large tenant counts do not scale gracefully this way
The alternative is to not share the sidecar at all: run one Manifold + OPA pair per tenant. Then the sidecar is the tenant, data needs no tenant level, and there is nothing for input.fromHeaders to resolve.
Deployment |
|
One Manifold + OPA serving several tenants | Required — the decision input is the only thing that tells tenants apart |
One Manifold + OPA pair per tenant | Not needed — the sidecar implicitly identifies the tenant |
Tool catalog for policy authoring
Writing a policy requires knowing every <server>/<tool> pair that exists, but tools/list only ever shows what the caller is already allowed to see. GET /mcp/list?tools=true returns the unfiltered catalog instead: when authz.enabled is false it's open to anyone, and when true it queries decisionPath.catalog the same way tools/call queries decisionPath.call — identified by headers.userID / headers.userGroups, and denying (403 {"error": "forbidden"}) on a missing identity, a policy deny, or a Decider error, without ever falling back to a static allowlist.
{
"mcp": [
{
"name": "petstore",
"description": "Swagger Petstore sample API",
"tools": [
{"name": "getpetbyid", "description": "Find pet by ID."}
]
},
// A WebMCP reverse server's tools only exist per-browser-connection, so
// it reports "dynamic" instead of a tool list.
{"name": "billing-svc", "description": "browser app", "dynamic": true},
// A backend that failed to connect still lists (with "error" instead of
// "tools") rather than dropping out of the response.
{"name": "crm", "description": "CRM MCP backend", "error": "connect: dial tcp: connection refused"}
]
}Disabling authorization per tenant
A fronting proxy that multiplexes several tenants behind one Manifold deployment can disable authz for a single request without flipping authz.enabled globally: set headers.bypass (default x-authz-bypass) to the exact string true. Any other value — True, 1, empty, or the header missing — goes through the normal authz checks (fail-closed).
When bypassed, for that request:
tools/callskips OPA and reaches the tool directlytools/listreturns the backend's full tool list, unfilteredGET /mcp/list?tools=truereturns200with the full catalog without queryingdecisionPath.catalog
This is equivalent to authz.enabled: false for that one request. Manifold logs decision: bypass (with server / method, no identity — none was resolved) so bypassed requests are distinguishable from allow / deny in an audit trail.
Fail-closed behavior
Every ambiguous or failing case denies the request rather than allowing it:
A missing or empty
headers.userID/headers.userGroupsdenies without querying OPAA missing or empty header for a required field configured in
input.fromHeadersdenies the same way, without querying OPA.requireddefaults totrue; a field withrequired: falseis omitted from the input instead of denyingAn
input.fromHeadersvalue that doesn't parse as its configuredtype(e.g.type: numberon a non-numeric header) denies without querying OPA, regardless ofrequiredA non-200 response, a response missing the expected
resultfield, a timeout, or a connection failure to OPA all denytools/listfiltering is a convenience — it hides tools the caller cannot use so they don't clutter a client's tool picker — but it is not the enforcement point. Enforcement happens ontools/call; a client that already knows a tool's name (e.g. from a stale list) is still denied thereA reverse (WebMCP)
mcpServersentry always registers acreate_pairing_codetool (seedocs/design/webmcp-reverse-gateway.md), andauthz.enabledcovers it like any other tool. A group that should be able to pair with such a server needs<server>/create_pairing_codein its policy, or pairing itself is deniedThis also holds one level down, inside OPA itself: if a bundle fetch fails, OPA keeps enforcing with the last bundle it activated — a bundle server outage stops policy updates, not decisions. But if OPA has never activated a bundle since startup (the bundle server was unreachable at boot, for example),
datastays empty and every decision comes backfalse/[], which fail-closes the same way. Bundle fetch failures are still worth alerting on — see "Operating recommendations" below
Operating recommendations
Enable OPA's decision log for an audit trail of every
allow/allowed_tools/allow_catalogquery. Each event should carry the decision, the same fields Manifold sent in that decision's input, and the revision of the policy data that produced it — without a data revision there's no way to tell which policy version a given decision was made under. The input fields differ per decision kind (see "Decision contract" above); the names below are theauthz.inputdefaults, each of which is renameable:Decision
Query
Input fields
allowtools/calluser,groups,server,toolallowed_toolstools/listuser,groups, and atoolsarray of{server, name}entriesallow_catalogGET /mcp/list?tools=trueuser,groupsEvery
input.fromHeadersfield that resolved is present in all three, at the top level. A field withrequired: falseis absent from the input on requests whose header was missing or empty, so a decision log missing it is expected rather than a dropped field.Distribute policy and data as an OPA bundle served over HTTP rather than mounting local files, so policy updates don't require restarting the sidecar. Bundle mode also stamps every decision log event with
bundles.<name>.revision, which is where that revision comes fromMonitor OPA's bundle fetch status (see "Fail-closed behavior" above for what a failure does to enforcement): OPA's Health API (
GET /health?bundles=true) reports unhealthy until every configured bundle has been activated at least once, so it doubles as a readiness probe. The status API and decision log also surface fetch failures
See examples/opa/ for a runnable OPA sidecar with sample policy and data.
HTTP endpoints
The HTTP endpoints exposed by Manifold.
MCP
Method | Path | Description |
|
| MCP requests (Streamable HTTP) |
|
| List registered servers (names and descriptions). Add |
OAuth 2.1
Method | Path | Description |
|
| Authorization Server metadata |
|
| Protected Resource metadata |
|
| Redirect to the login page |
|
| OAuth callback |
|
| Token issuance |
|
| Dynamic client registration (RFC 7591) |
|
| Aliases without a server name |
|
| Aliases without a server name |
Other
Method | Path | Description |
|
| Download stored content (only when |
|
| Prometheus metrics (only when |
Development
See CONTRIBUTING.md for how to set up a development environment and submit changes.
Test
make testLint
make lintInspiration
This project is inspired by the Agent / MCP Gateway of LiteLLM.
Just as LiteLLM's MCP Gateway provides a unified access point to multiple MCP servers, Manifold aims to be a gateway that connects a single MCP interface to many MCP servers / REST APIs.
License
MIT License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseCqualityDmaintenanceA powerful gateway for the Model Context Protocol (MCP) that unifies AI toolchains by federating multiple MCP servers, wrapping REST APIs as MCP tools, and supporting multiple transport methods with an admin dashboard.1
- -licenseNot gradedqualityNot gradedmaintenanceA feature-rich Model Context Protocol gateway that federates MCP and REST services, unifying discovery, authentication, and transport protocols while providing virtualization of legacy APIs as MCP-compliant tools.
- AlicenseNot gradedqualityCmaintenanceA gateway that aggregates multiple MCP servers into a single endpoint, namespacing their tools and forwarding calls, so an agent connects to one MCP to access the entire stack.MIT
- AlicenseNot gradedqualityDmaintenanceA gateway that aggregates multiple MCP servers into a single endpoint with authentication, group-based access control, and audit logging for AI agents.22MIT
Related MCP Connectors
MCP server for AI access to Swagger by SmartBear.
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nonchan7720/manifold'
If you have feedback or need assistance with the MCP directory API, please join our Discord server