SynaMCPs MCP Server
Cung cấp một cổng kết nối phổ quát cho các công cụ AI doanh nghiệp, hỗ trợ lưu trữ tri thức, kiểm soát truy cập và proxy các nguồn AI bên ngoài.
Tài liệu
Synamcps (SynaMCPs) — MCP + Knowledge Storage Gateway
Synamcps is a server that provides:
- An MCP server for LLM clients (Cursor / Claude Desktop / Claude Code / etc.)
- An HTTP API to create/search/read knowledge items
- A Web Admin UI to manage users/groups/storages/tokens, view status, and do basic diagnostics
- Token-based access to storages (tokens only narrow permissions), with ACL/RBAC, rate limiting, and usage/metrics
The server supports multiple auth methods (OIDC/Keycloak/Google/Teleport Proxy JWT) and an internal login for the Admin UI.
Quick start (Docker Compose)
Requirements:
- Docker + Docker Compose
Run:
cp .env.example .env
make compose-up
Open:
- Web UI:
http://localhost:8080/login - MCP endpoint (streamable):
http://localhost:8080/mcp - HTTP API:
http://localhost:8080/api/*
Stop:
make compose-down
Local run (without Docker)
Requirements:
- Go 1.23+
- Postgres, Redis, S3/MinIO available (or use Docker Compose as your infrastructure)
export CONFIG_PATH=configs/config.local.yaml
go run ./cmd/server
Features (high level)
Storages, ACL and tokens
- Storage is a logical entity tied to:
- records in the metadata catalog (Postgres)
- an S3 prefix (
storage.S3Prefix) - search scoping (vector backend: pgvector/qdrant)
- ACL bindings define user/group access to a storage (read/write/admin/owner).
- Access tokens:
- belong to a user (owner)
- do not expand permissions — they narrow the owner's access (intersection of user ACL and token scopes)
- can restrict:
storageIds,maxMode(read/read_write),toolAllowlist, rate limits.
- Document visibility (
personal/group/public) is enforced on top of storage access: being able to read a storage is necessary but not sufficient — apersonaldocument is visible only to its owner, agroupdocument only to its owner or members of its groups.
Knowledge items
- You can add an item as:
- Text (via the standard
POST /api/knowledge) - File (upload → raw stored to S3 → best-effort extraction → summary+embeddings → item saved to storage)
- Link (download → raw stored to S3 → extraction → summary+embeddings → item saved to storage)
- Text (via the standard
- Link ingestion only accepts
http/httpsURLs and refuses to fetch internal addresses (loopback, link-local incl. cloud metadata169.254.169.254, and private ranges) — SSRF protection that also applies across redirects.
MCP
- MCP exposes a dynamic
tools/listbased on the bearer token (only allowed tools/storages are visible). - Supports Streamable HTTP transport (
/mcp) and optional legacy SSE. - MCP tool names use
_(underscore) to avoid filtering/warnings in some clients. - MCP proxy: register upstream HTTP/SSE MCP servers in Admin UI (
MCP Serverstab), discover tools/resources/prompts, restrict by ACL and per-token scopes. Proxied identifiers:- tools/prompts:
{slug}__{upstream_name} - resources:
syna-mcp/{slug}/{upstream_uri} - the
slugis generated automatically from the server name (no manual entry).
- tools/prompts:
- Upstream auth secrets are stored encrypted in Postgres (
MCP_PROXY_SECRETS_KEYin.env).
Usage / Rate limit / Metrics
- Rate limiting per token (minute/hour/day + burst), enforced for both MCP calls and the REST API (
429 Too Many Requestswhen exceeded). - Request bodies are capped by
limits.max_upload_bytes(413when exceeded). - Usage events (and status/errors) can be written to Redis TimeSeries (when enabled).
/metricsexposes Prometheus-format metrics (label values are sanitized and series cardinality is bounded).
Web Admin UI
The built-in admin UI (server-rendered HTML) lets you:
- Users / Groups / Group members
- Storages + Storage details (ACL, keys/tokens, items list)
- Tokens + MCP Connect wizard + delete
- Add item (Text/File/Link)
- Search (by token / by storage)
- Status (Postgres/Redis/S3/LLMs + error counters)
Forms pick entities from name dropdowns (storages/groups/users/tokens/MCP servers) with refresh buttons instead of typing raw IDs. Slugs are no longer entered by hand — a storage defaults its slug to its id and an MCP server derives a unique slug from its name.
Configuration
Default config: configs/config.example.yaml
Override: CONFIG_PATH=/path/to/config.yaml
Key sections:
web.default_admin: Admin UI username/password (password via env ref)oauth.providers: OIDC providers (issuer/audience/jwks_url)teleport: Teleport Proxy JWT (issuer/audience)redis: sessions + usage/time-series (when enabled)s3: endpoint/bucket + large document thresholdembedding,summarization: LLMs (provider/model/api/api_key_env_ref)vector_backend.active:pgvectororqdrantmetadata_catalog.dsn: Postgres DSNapi.allowed_origins: strict CORS allowlistusage: accounting and time series, retention, exporters
Example .env for local development: .env.example.
HTTP API
Authentication:
- cookieAuth: web UI session (
session_idcookie) - bearerAuth:
Authorization: Bearer <token>
Common error codes:
401— missing token/session403— forbidden (insufficient permissions for the storage/operation)404— not found413— request body exceedslimits.max_upload_bytes422— invalid request429— rate limit exceeded (per-token limits)
Knowledge API
GET /api/knowledge
List items with pagination and filters.
Query params:
page(int)pageSize(int)storageId(string) — limit to a specific storagesource(string) — exact matchsourceUrl(string)sourceUrlMode(exact|partial) —partialworks only whensearch.filters.source_url.allow_partial_match=true
Response: models.PaginatedKnowledgeList (items + total + hasNext + page/pageSize).
POST /api/knowledge
Create an item from text.
Body:
{
"storageId": "storage-id-optional",
"title": "Runbook",
"text": "Long knowledge text...",
"mimeType": "text/plain",
"visibility": "personal",
"groupIds": [],
"source": "api",
"sourceUrl": "https://docs.example.com/runbook"
}
Notes:
- if
storageIdis empty and access-service is enabled, the server uses/creates the user's personal storage visibilitydefaults topersonalgroupIdsmust be an array (notnull)
GET /api/knowledge/{docId}
Return a single document.
DELETE /api/knowledge/{docId}
Delete a document (and associated embeddings in the vector store).
POST /api/knowledge/search
Embedding-based search.
Body:
{
"query": "kubernetes ingress timeout",
"topK": 10,
"filters": {
"storageId": "storage-id-optional",
"source": "api",
"sourceUrl": "https://...",
"sourceUrlMode": "exact"
}
}
Response: an array of search hits (including snippet/title/source/sourceUrl).
Ingest API (File/Link)
POST /api/knowledge/ingest/file (multipart)
Upload a file as an item:
- raw content is stored in S3
- best-effort text extraction is performed
- the pipeline produces summary + embeddings
- the final result is saved as a normal knowledge item
Multipart fields:
storageId(string, optional)title(string, optional)visibility(personal|group|public, optional)source(string, optional)sourceUrl(string, optional)mimeType(string, optional)file(required)
Example:
curl -X POST http://localhost:8080/api/knowledge/ingest/file \
-H "Authorization: Bearer $TOKEN" \
-F "storageId=..." \
-F "title=Spec" \
-F "visibility=personal" \
-F "file=@./spec.txt"
POST /api/knowledge/ingest/link (json)
Downloads a URL, stores raw content to S3, extracts text, and saves an item.
Body:
{
"storageId": "storage-id-optional",
"title": "Optional title",
"url": "https://example.com/docs",
"visibility": "personal",
"source": "link"
}
Admin API (/api/admin/*)
All endpoints require authentication (cookie or bearer), and many require platform_admin.
Users
GET /api/admin/meGET /api/admin/users(platform_admin)POST /api/admin/users(platform_admin)GET /api/admin/users/{id}(admin или сам пользователь)PATCH /api/admin/users/{id}(admin или сам пользователь)POST /api/admin/users/{id}/password(admin или сам пользователь)DELETE /api/admin/users/{id}(platform_admin)
Groups
GET /api/admin/groups(platform_admin)POST /api/admin/groups(platform_admin)DELETE /api/admin/groups/{id}(platform_admin)GET /api/admin/groups/{id}/members(platform_admin)PUT /api/admin/groups/{id}/members/{userId}(platform_admin)DELETE /api/admin/groups/{id}/members/{userId}(platform_admin)
Storages
GET /api/admin/storages(storages available to the current user)POST /api/admin/storagesDELETE /api/admin/storages/{id}(requiresstorage.delete: storage owner/admin or platform_admin)GET /api/admin/storages/{id}(storage details: storage + acl + tokens; requires read access)GET /api/admin/storages/{id}/acl(requiresacl.manage)PUT /api/admin/storages/{id}/acl(requiresacl.manage)
Tokens
Mutating token endpoints require the caller to be the token owner or platform_admin; GET /api/admin/tokens lists only the caller's own tokens (platform_admin sees all).
GET /api/admin/tokensPOST /api/admin/tokensDELETE /api/admin/tokens/{id}(owner/platform_admin)PATCH /api/admin/tokens/{id}/rate-limit(owner/platform_admin)POST /api/admin/tokens/{id}/revoke(owner/platform_admin)POST /api/admin/tokens/{id}/rotate(owner/platform_admin)PATCH /api/admin/tokens/{id}/mcp-scopes(owner/platform_admin)GET/POST /api/admin/tokens/{id}/connect-options(wizard for MCP clients)
Usage / Status
GET /api/admin/usage/seriesGET /api/admin/usage/summaryGET /api/admin/status— component status + error counters (Redis TimeSeries)
MCP
Transport
- Streamable HTTP:
POST /mcp(JSON-RPC) +GET /mcp(SSE stream поMcp-Session-Id) - Legacy SSE (если включено):
/sse+/messages
Minimal flow (streamable)
- Obtain a bearer token (OIDC/Teleport/or internal)
initialize:
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{}}'
- Save
Mcp-Session-Idfrom headers/response (clients do this automatically) - Open the stream (the bearer token is required and must match the session owner):
curl -N http://localhost:8080/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Mcp-Session-Id: <session_id>"
GET /mcp and DELETE /mcp are authenticated; a session can only be read/closed by the principal that created it.
Dynamic tools/list
tools/list returns only the tools allowed by the current bearer token and its storage scopes.
tools/call
tools/call routes calls to the corresponding internal methods (knowledge_* etc.).
MCP Connect (Web UI)
The Admin UI includes an MCP Connect page that generates:
- config file name
configBody(JSON)- step-by-step instructions
You can copy configBody by clicking it.
Installation and operations
Docker Compose
cp .env.example .env
make compose-up
Helpful:
make compose-downmake seed-dev(if used in your environment)
Config and secrets
configs/config.example.yaml— example configconfigs/config.local.yaml— local compose config (used indocker-compose.yml).env— secrets (passwords/keys), example in.env.example
CORS
api.allowed_origins is a strict allowlist. Unknown origins are rejected.
Web UI routes (/, /login, /logout, /app*) bypass the origin check.
Troubleshooting
- Origin not allowed:
- add the origin to
api.allowed_origins - make sure you open the Web UI via
/login(web routes bypass origin-check)
- add the origin to
- Invalid credentials:
- verify
.envis loaded (in compose it is wired viaenv_file: .env) - verify
web.default_admin.password_env_ref
- verify
- Time series are not created:
- you need Redis with the RedisTimeSeries module (
TS.ADDmust be supported) - or disable
usage.redis_timeseries
- you need Redis with the RedisTimeSeries module (
- MCP tools “filtered out” warning:
- tool names already use
_instead of.
- tool names already use
Documentation in this repo
docs/marketing.md— product overview + corporate RAG / agentic pipeline scenariosdocs/setup.md— installation and rundocs/api.md— basic knowledge endpointsdocs/mcp-connection.md— MCP connectiondocs/auth-setup.md— auth providersdocs/openapi.yaml— baseline OpenAPI stub