@thotischner/observability-mcp 1.7.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/products.yaml.example +48 -0
- package/dist/analysis/history.d.ts +70 -0
- package/dist/analysis/history.js +170 -0
- package/dist/analysis/history.test.d.ts +1 -0
- package/dist/analysis/history.test.js +141 -0
- package/dist/audit/log.d.ts +108 -0
- package/dist/audit/log.js +200 -0
- package/dist/audit/log.test.d.ts +1 -0
- package/dist/audit/log.test.js +147 -0
- package/dist/audit/middleware.d.ts +20 -0
- package/dist/audit/middleware.js +50 -0
- package/dist/audit/redaction-bypass.d.ts +67 -0
- package/dist/audit/redaction-bypass.js +64 -0
- package/dist/audit/redaction-bypass.test.d.ts +1 -0
- package/dist/audit/redaction-bypass.test.js +72 -0
- package/dist/audit/sinks/types.d.ts +18 -0
- package/dist/audit/sinks/types.js +1 -0
- package/dist/audit/sinks/webhook.d.ts +45 -0
- package/dist/audit/sinks/webhook.js +111 -0
- package/dist/audit/sinks/webhook.test.d.ts +1 -0
- package/dist/audit/sinks/webhook.test.js +162 -0
- package/dist/auth/credentials.d.ts +29 -0
- package/dist/auth/credentials.js +53 -1
- package/dist/auth/credentials.test.js +46 -1
- package/dist/auth/csrf.d.ts +26 -0
- package/dist/auth/csrf.js +128 -0
- package/dist/auth/csrf.test.d.ts +1 -0
- package/dist/auth/csrf.test.js +143 -0
- package/dist/auth/local-users.d.ts +68 -0
- package/dist/auth/local-users.js +154 -0
- package/dist/auth/local-users.test.d.ts +1 -0
- package/dist/auth/local-users.test.js +121 -0
- package/dist/auth/middleware.d.ts +49 -0
- package/dist/auth/middleware.js +65 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +90 -0
- package/dist/auth/oidc/client.d.ts +73 -0
- package/dist/auth/oidc/client.js +104 -0
- package/dist/auth/oidc/client.test.d.ts +1 -0
- package/dist/auth/oidc/client.test.js +121 -0
- package/dist/auth/oidc/dcr.d.ts +70 -0
- package/dist/auth/oidc/dcr.js +160 -0
- package/dist/auth/oidc/dcr.test.d.ts +1 -0
- package/dist/auth/oidc/dcr.test.js +109 -0
- package/dist/auth/oidc/discovery.d.ts +38 -0
- package/dist/auth/oidc/discovery.js +48 -0
- package/dist/auth/oidc/discovery.test.d.ts +1 -0
- package/dist/auth/oidc/discovery.test.js +68 -0
- package/dist/auth/oidc/endpoints.d.ts +20 -0
- package/dist/auth/oidc/endpoints.js +168 -0
- package/dist/auth/oidc/endpoints.test.d.ts +7 -0
- package/dist/auth/oidc/endpoints.test.js +304 -0
- package/dist/auth/oidc/flow-cookie.d.ts +57 -0
- package/dist/auth/oidc/flow-cookie.js +142 -0
- package/dist/auth/oidc/flow-cookie.test.d.ts +1 -0
- package/dist/auth/oidc/flow-cookie.test.js +0 -0
- package/dist/auth/oidc/index.d.ts +7 -0
- package/dist/auth/oidc/index.js +6 -0
- package/dist/auth/oidc/jwks.d.ts +36 -0
- package/dist/auth/oidc/jwks.js +69 -0
- package/dist/auth/oidc/jwks.test.d.ts +1 -0
- package/dist/auth/oidc/jwks.test.js +65 -0
- package/dist/auth/oidc/jwt.d.ts +62 -0
- package/dist/auth/oidc/jwt.js +113 -0
- package/dist/auth/oidc/jwt.test.d.ts +1 -0
- package/dist/auth/oidc/jwt.test.js +141 -0
- package/dist/auth/oidc/pkce.d.ts +19 -0
- package/dist/auth/oidc/pkce.js +43 -0
- package/dist/auth/oidc/pkce.test.d.ts +1 -0
- package/dist/auth/oidc/pkce.test.js +55 -0
- package/dist/auth/oidc/profiles.d.ts +22 -0
- package/dist/auth/oidc/profiles.js +95 -0
- package/dist/auth/oidc/profiles.test.d.ts +1 -0
- package/dist/auth/oidc/profiles.test.js +51 -0
- package/dist/auth/oidc/runtime.d.ts +66 -0
- package/dist/auth/oidc/runtime.js +142 -0
- package/dist/auth/oidc/runtime.test.d.ts +1 -0
- package/dist/auth/oidc/runtime.test.js +181 -0
- package/dist/auth/policy/batch-dry-run.d.ts +56 -0
- package/dist/auth/policy/batch-dry-run.js +129 -0
- package/dist/auth/policy/batch-dry-run.test.d.ts +1 -0
- package/dist/auth/policy/batch-dry-run.test.js +140 -0
- package/dist/auth/policy/engine.d.ts +64 -0
- package/dist/auth/policy/engine.js +87 -0
- package/dist/auth/policy/engine.test.d.ts +1 -0
- package/dist/auth/policy/engine.test.js +98 -0
- package/dist/auth/policy/loader.d.ts +45 -0
- package/dist/auth/policy/loader.js +137 -0
- package/dist/auth/policy/loader.test.d.ts +1 -0
- package/dist/auth/policy/loader.test.js +86 -0
- package/dist/auth/policy/opa.d.ts +69 -0
- package/dist/auth/policy/opa.js +173 -0
- package/dist/auth/policy/opa.test.d.ts +1 -0
- package/dist/auth/policy/opa.test.js +206 -0
- package/dist/auth/rbac.d.ts +62 -0
- package/dist/auth/rbac.js +162 -0
- package/dist/auth/rbac.test.d.ts +1 -0
- package/dist/auth/rbac.test.js +183 -0
- package/dist/auth/session.d.ts +66 -0
- package/dist/auth/session.js +146 -0
- package/dist/auth/session.test.d.ts +1 -0
- package/dist/auth/session.test.js +90 -0
- package/dist/catalog/loader.d.ts +67 -0
- package/dist/catalog/loader.js +122 -0
- package/dist/catalog/loader.test.d.ts +1 -0
- package/dist/catalog/loader.test.js +108 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/inspector-config.d.ts +9 -0
- package/dist/cli/inspector-config.js +28 -0
- package/dist/cli/inspector-config.test.d.ts +1 -0
- package/dist/cli/inspector-config.test.js +33 -0
- package/dist/cli/lib.d.ts +1 -1
- package/dist/cli/lib.js +1 -0
- package/dist/conformance/mcp-2025-11-25.test.d.ts +1 -0
- package/dist/conformance/mcp-2025-11-25.test.js +206 -0
- package/dist/connectors/interface.d.ts +5 -1
- package/dist/connectors/loader.js +6 -4
- package/dist/connectors/loader.test.d.ts +1 -0
- package/dist/connectors/loader.test.js +78 -0
- package/dist/connectors/prometheus.test.js +31 -13
- package/dist/connectors/registry.d.ts +13 -0
- package/dist/connectors/registry.js +30 -0
- package/dist/connectors/registry.test.js +56 -2
- package/dist/context.d.ts +45 -1
- package/dist/context.js +40 -1
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +58 -0
- package/dist/federation/registry.d.ts +32 -0
- package/dist/federation/registry.js +77 -0
- package/dist/federation/registry.test.d.ts +1 -0
- package/dist/federation/registry.test.js +130 -0
- package/dist/federation/upstream.d.ts +60 -0
- package/dist/federation/upstream.js +114 -0
- package/dist/index.js +2124 -73
- package/dist/middleware/ssrfGuard.d.ts +15 -0
- package/dist/middleware/ssrfGuard.js +103 -0
- package/dist/middleware/ssrfGuard.test.d.ts +1 -0
- package/dist/middleware/ssrfGuard.test.js +81 -0
- package/dist/net/egress-policy.js +2 -0
- package/dist/observability/otel.d.ts +20 -0
- package/dist/observability/otel.js +118 -0
- package/dist/observability/otel.test.d.ts +1 -0
- package/dist/observability/otel.test.js +56 -0
- package/dist/openapi.js +654 -6
- package/dist/openapi.test.d.ts +1 -0
- package/dist/openapi.test.js +98 -0
- package/dist/policy/redact.d.ts +44 -0
- package/dist/policy/redact.js +144 -0
- package/dist/policy/redact.test.d.ts +1 -0
- package/dist/policy/redact.test.js +172 -0
- package/dist/postmortem/synthesizer.d.ts +83 -0
- package/dist/postmortem/synthesizer.js +205 -0
- package/dist/postmortem/synthesizer.test.d.ts +1 -0
- package/dist/postmortem/synthesizer.test.js +141 -0
- package/dist/products/loader.d.ts +112 -0
- package/dist/products/loader.js +289 -0
- package/dist/products/loader.test.d.ts +1 -0
- package/dist/products/loader.test.js +257 -0
- package/dist/quota/charge.d.ts +28 -0
- package/dist/quota/charge.js +30 -0
- package/dist/quota/charge.test.d.ts +1 -0
- package/dist/quota/charge.test.js +83 -0
- package/dist/quota/limiter.d.ts +97 -0
- package/dist/quota/limiter.js +161 -0
- package/dist/quota/limiter.test.d.ts +1 -0
- package/dist/quota/limiter.test.js +205 -0
- package/dist/quota/token-budget.d.ts +119 -0
- package/dist/quota/token-budget.js +297 -0
- package/dist/quota/token-budget.test.d.ts +1 -0
- package/dist/quota/token-budget.test.js +215 -0
- package/dist/scim/group-role-map.d.ts +4 -0
- package/dist/scim/group-role-map.js +33 -0
- package/dist/scim/group-role-map.test.d.ts +1 -0
- package/dist/scim/group-role-map.test.js +33 -0
- package/dist/scim/routes.d.ts +15 -0
- package/dist/scim/routes.js +249 -0
- package/dist/scim/store.d.ts +37 -0
- package/dist/scim/store.js +178 -0
- package/dist/scim/store.test.d.ts +1 -0
- package/dist/scim/store.test.js +121 -0
- package/dist/scim/types.d.ts +73 -0
- package/dist/scim/types.js +29 -0
- package/dist/sdk/hooks.d.ts +77 -0
- package/dist/sdk/hooks.js +72 -0
- package/dist/sdk/hooks.test.d.ts +1 -0
- package/dist/sdk/hooks.test.js +159 -0
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/manifest-schema.d.ts +17 -0
- package/dist/sdk/manifest-schema.js +21 -0
- package/dist/tenancy/context.d.ts +45 -0
- package/dist/tenancy/context.js +97 -0
- package/dist/tenancy/context.test.d.ts +1 -0
- package/dist/tenancy/context.test.js +72 -0
- package/dist/tenancy/migration.test.d.ts +7 -0
- package/dist/tenancy/migration.test.js +75 -0
- package/dist/tools/context-seam.test.js +6 -1
- package/dist/tools/detect-anomalies.d.ts +1 -1
- package/dist/tools/detect-anomalies.js +5 -4
- package/dist/tools/generate-postmortem.d.ts +35 -0
- package/dist/tools/generate-postmortem.js +191 -0
- package/dist/tools/get-anomaly-history.d.ts +35 -0
- package/dist/tools/get-anomaly-history.js +126 -0
- package/dist/tools/get-service-health.d.ts +1 -1
- package/dist/tools/get-service-health.js +4 -3
- package/dist/tools/list-services.d.ts +1 -1
- package/dist/tools/list-services.js +3 -2
- package/dist/tools/list-sources.d.ts +1 -1
- package/dist/tools/list-sources.js +6 -2
- package/dist/tools/query-logs.d.ts +1 -1
- package/dist/tools/query-logs.js +2 -2
- package/dist/tools/query-metrics.d.ts +1 -1
- package/dist/tools/query-metrics.js +19 -6
- package/dist/tools/query-traces.d.ts +47 -0
- package/dist/tools/query-traces.js +145 -0
- package/dist/tools/query-traces.test.d.ts +1 -0
- package/dist/tools/query-traces.test.js +110 -0
- package/dist/tools/registry-names.d.ts +35 -0
- package/dist/tools/registry-names.js +54 -0
- package/dist/tools/registry-names.test.d.ts +1 -0
- package/dist/tools/registry-names.test.js +61 -0
- package/dist/tools/topology.d.ts +3 -3
- package/dist/tools/topology.js +10 -6
- package/dist/topology/merge.d.ts +22 -0
- package/dist/topology/merge.js +178 -0
- package/dist/topology/merge.test.d.ts +1 -0
- package/dist/topology/merge.test.js +110 -0
- package/dist/transport/sessionStore.d.ts +66 -0
- package/dist/transport/sessionStore.js +138 -0
- package/dist/transport/sessionStore.test.d.ts +1 -0
- package/dist/transport/sessionStore.test.js +118 -0
- package/dist/transport/websocket.d.ts +35 -0
- package/dist/transport/websocket.js +133 -0
- package/dist/transport/websocket.test.d.ts +1 -0
- package/dist/transport/websocket.test.js +124 -0
- package/dist/types.d.ts +51 -0
- package/dist/ui/index.html +3083 -88
- package/package.json +32 -5
package/dist/openapi.js
CHANGED
|
@@ -23,6 +23,10 @@ const SOURCE_SCHEMA = {
|
|
|
23
23
|
},
|
|
24
24
|
tls: { type: "object", additionalProperties: true },
|
|
25
25
|
signalType: { type: "string", enum: ["metrics", "logs", "traces"] },
|
|
26
|
+
tenant: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Tenant this source belongs to. Omitted = global (visible to every tenant). Tagged sources are visible only inside their named tenant; cross-tenant probes return 404 with no existence leak.",
|
|
29
|
+
},
|
|
26
30
|
},
|
|
27
31
|
additionalProperties: true,
|
|
28
32
|
};
|
|
@@ -51,26 +55,36 @@ export function buildOpenApiSpec(version) {
|
|
|
51
55
|
{ name: "settings", description: "Runtime server configuration." },
|
|
52
56
|
{ name: "metrics-config", description: "Per-source metric definitions." },
|
|
53
57
|
{ name: "self", description: "Server liveness and Prometheus metrics." },
|
|
58
|
+
{ name: "auth", description: "Management-plane session login / logout / identity." },
|
|
59
|
+
{ name: "audit", description: "Tamper-evident audit log of /api/* mutations." },
|
|
60
|
+
{ name: "usage", description: "Per-identity rate-limit snapshot for /mcp callers." },
|
|
61
|
+
{ name: "catalog", description: "Operator-curated service catalog." },
|
|
54
62
|
],
|
|
55
63
|
paths: {
|
|
56
64
|
"/api/sources": {
|
|
57
65
|
get: {
|
|
58
66
|
tags: ["sources"],
|
|
59
|
-
summary: "List configured sources with live health.",
|
|
67
|
+
summary: "List configured sources with live health (tenant-scoped).",
|
|
68
|
+
description: "Non-admin callers see only their own tenant's sources + globals (untagged). Admins (users:delete) see every source; pass ?tenant=X for an admin drill-down to that tenant + globals. Anonymous mode bypasses scoping (single-tenant default).",
|
|
69
|
+
parameters: [
|
|
70
|
+
{ name: "tenant", in: "query", schema: { type: "string" }, description: "Admin-only tenant drill-down (silently ignored for non-admins, who are scoped to their own tenant)." },
|
|
71
|
+
],
|
|
60
72
|
responses: {
|
|
61
73
|
"200": {
|
|
62
|
-
description: "Sources with status, latency, signal type.",
|
|
74
|
+
description: "Sources with status, latency, signal type. The `tenant` field is present when the source is tagged.",
|
|
63
75
|
content: { "application/json": { schema: { type: "array", items: SOURCE_SCHEMA } } },
|
|
64
76
|
},
|
|
65
77
|
},
|
|
66
78
|
},
|
|
67
79
|
post: {
|
|
68
80
|
tags: ["sources"],
|
|
69
|
-
summary: "Add a new source.",
|
|
81
|
+
summary: "Add a new source (tenant-aware).",
|
|
82
|
+
description: "Body may include `tenant` to tag the source. Non-admins may only create within their own tenant; setting body.tenant to another value returns 403. Admins may leave tenant unset (global) or set any value.",
|
|
70
83
|
requestBody: { required: true, content: { "application/json": { schema: SOURCE_SCHEMA } } },
|
|
71
84
|
responses: {
|
|
72
85
|
"201": { description: "Source created." },
|
|
73
86
|
"400": { description: "Validation error." },
|
|
87
|
+
"403": { description: "Non-admin attempting to create in another tenant." },
|
|
74
88
|
"409": { description: "Source with that name already exists." },
|
|
75
89
|
},
|
|
76
90
|
},
|
|
@@ -103,14 +117,23 @@ export function buildOpenApiSpec(version) {
|
|
|
103
117
|
parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
|
|
104
118
|
put: {
|
|
105
119
|
tags: ["sources"],
|
|
106
|
-
summary: "Replace an existing source.",
|
|
120
|
+
summary: "Replace an existing source (tenant-aware).",
|
|
121
|
+
description: "Non-admin probes of a cross-tenant source return 404 (no existence leak — same posture as /api/products). Non-admins attempting to reassign body.tenant return 403. Admins may move sources between tenants.",
|
|
107
122
|
requestBody: { required: true, content: { "application/json": { schema: SOURCE_SCHEMA } } },
|
|
108
|
-
responses: {
|
|
123
|
+
responses: {
|
|
124
|
+
"200": { description: "Updated." },
|
|
125
|
+
"403": { description: "Non-admin attempting tenant reassignment." },
|
|
126
|
+
"404": { description: "Not found (or hidden by tenant scope)." },
|
|
127
|
+
},
|
|
109
128
|
},
|
|
110
129
|
delete: {
|
|
111
130
|
tags: ["sources"],
|
|
112
131
|
summary: "Remove a source.",
|
|
113
|
-
|
|
132
|
+
description: "Cross-tenant deletes return 404 (no existence leak).",
|
|
133
|
+
responses: {
|
|
134
|
+
"204": { description: "Removed." },
|
|
135
|
+
"404": { description: "Not found (or hidden by tenant scope)." },
|
|
136
|
+
},
|
|
114
137
|
},
|
|
115
138
|
},
|
|
116
139
|
"/api/source-types": {
|
|
@@ -125,6 +148,35 @@ export function buildOpenApiSpec(version) {
|
|
|
125
148
|
},
|
|
126
149
|
},
|
|
127
150
|
},
|
|
151
|
+
"/api/tools/registry": {
|
|
152
|
+
get: {
|
|
153
|
+
tags: ["products"],
|
|
154
|
+
summary: "MCP tool catalogue — name + category + one-line summary, used by the Products picker.",
|
|
155
|
+
description: "Static metadata derived from REGISTERED_TOOLS. The Products modal pulls this to populate a multi-select picker grouped by category (discovery / query / diagnose / topology); the server-side typo guard (PR #343) stays as defence-in-depth.",
|
|
156
|
+
responses: {
|
|
157
|
+
"200": {
|
|
158
|
+
description: "Tool registry.",
|
|
159
|
+
content: { "application/json": { schema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
tools: {
|
|
163
|
+
type: "array",
|
|
164
|
+
items: {
|
|
165
|
+
type: "object",
|
|
166
|
+
required: ["name", "category", "summary"],
|
|
167
|
+
properties: {
|
|
168
|
+
name: { type: "string" },
|
|
169
|
+
category: { type: "string", enum: ["discovery", "query", "diagnose", "topology"] },
|
|
170
|
+
summary: { type: "string" },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
} } },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
128
180
|
"/api/services": {
|
|
129
181
|
get: {
|
|
130
182
|
tags: ["services"],
|
|
@@ -180,6 +232,602 @@ export function buildOpenApiSpec(version) {
|
|
|
180
232
|
responses: { "200": { description: "OpenAPI 3.1 document." } },
|
|
181
233
|
},
|
|
182
234
|
},
|
|
235
|
+
"/api/info": {
|
|
236
|
+
get: {
|
|
237
|
+
tags: ["self"],
|
|
238
|
+
summary: "Server identity, build info, plugin list and governance posture.",
|
|
239
|
+
description: "Anonymous-readable snapshot for external dashboards and discovery probes. " +
|
|
240
|
+
"The `governance` block surfaces the active management-plane configuration " +
|
|
241
|
+
"as booleans / rate-limit number only — no file paths, no session secret, " +
|
|
242
|
+
"no user counts. Useful for alerting on \"this deployment silently reverted " +
|
|
243
|
+
"to anonymous mode\" or \"redaction is off in prod\".",
|
|
244
|
+
responses: {
|
|
245
|
+
"200": {
|
|
246
|
+
description: "Server info + governance posture.",
|
|
247
|
+
content: {
|
|
248
|
+
"application/json": {
|
|
249
|
+
schema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
name: { type: "string" },
|
|
253
|
+
version: { type: "string" },
|
|
254
|
+
mcpProtocolVersion: { type: "string" },
|
|
255
|
+
build: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
commit: { type: ["string", "null"] },
|
|
259
|
+
date: { type: ["string", "null"] },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
runtime: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
node: { type: "string" },
|
|
266
|
+
platform: { type: "string" },
|
|
267
|
+
arch: { type: "string" },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
governance: {
|
|
271
|
+
type: "object",
|
|
272
|
+
description: "Active management-plane posture; booleans + rate-limit number only.",
|
|
273
|
+
properties: {
|
|
274
|
+
authMode: { type: "string", enum: ["anonymous", "basic", "oidc"] },
|
|
275
|
+
authSecretEphemeral: {
|
|
276
|
+
type: "boolean",
|
|
277
|
+
description: "True when OMCP_SESSION_SECRET is unset and the server minted an in-memory secret at boot. Sessions don't survive a restart.",
|
|
278
|
+
},
|
|
279
|
+
oidcIssuer: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Active OIDC issuer URL. Empty string when authMode is not 'oidc'. Never includes the client_secret.",
|
|
282
|
+
},
|
|
283
|
+
auditPersisted: {
|
|
284
|
+
type: "boolean",
|
|
285
|
+
description: "True when OMCP_MGMT_AUDIT_FILE is set; false means the audit log is the in-memory 500-entry ring.",
|
|
286
|
+
},
|
|
287
|
+
catalogConfigured: { type: "boolean" },
|
|
288
|
+
redaction: { type: "boolean" },
|
|
289
|
+
trustProxy: { type: "boolean" },
|
|
290
|
+
toolRatePerMin: { type: "integer" },
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
plugins: {
|
|
294
|
+
type: "array",
|
|
295
|
+
items: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
name: { type: "string" },
|
|
299
|
+
source: { type: "string" },
|
|
300
|
+
version: { type: ["string", "null"] },
|
|
301
|
+
signalTypes: { type: ["array", "null"], items: { type: "string" } },
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
"/api/me": {
|
|
314
|
+
get: {
|
|
315
|
+
tags: ["auth"],
|
|
316
|
+
summary: "Current identity, mode and granted permissions.",
|
|
317
|
+
responses: {
|
|
318
|
+
"200": {
|
|
319
|
+
description: "Identity snapshot. `authenticated: false` in anonymous mode or when the session cookie is missing/invalid.",
|
|
320
|
+
content: {
|
|
321
|
+
"application/json": {
|
|
322
|
+
schema: {
|
|
323
|
+
type: "object",
|
|
324
|
+
properties: {
|
|
325
|
+
authenticated: { type: "boolean" },
|
|
326
|
+
mode: { type: "string", enum: ["anonymous", "basic", "oidc"] },
|
|
327
|
+
user: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
sub: { type: "string" },
|
|
331
|
+
name: { type: "string" },
|
|
332
|
+
email: { type: "string", description: "Present when the IdP supplied a verified email claim (OIDC mode)." },
|
|
333
|
+
roles: { type: "array", items: { type: "string" } },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
permissions: {
|
|
337
|
+
type: "array",
|
|
338
|
+
items: {
|
|
339
|
+
type: "object",
|
|
340
|
+
properties: {
|
|
341
|
+
resource: { type: "string" },
|
|
342
|
+
action: { type: "string", enum: ["read", "write", "delete", "bypass"] },
|
|
343
|
+
// Resource enum is unconstrained at the OpenAPI level so
|
|
344
|
+
// custom policies loaded via OMCP_RBAC_POLICY_FILE that
|
|
345
|
+
// (correctly) include all built-in resources still validate.
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
exp: { type: "integer", description: "Cookie expiry (seconds since epoch)." },
|
|
350
|
+
idpIssuer: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "Active OIDC issuer URL. Present only when mode === \"oidc\". Useful for UI badges or IdP-side profile links.",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
"/api/auth/login": {
|
|
363
|
+
post: {
|
|
364
|
+
tags: ["auth"],
|
|
365
|
+
summary: "Sign in (basic mode only).",
|
|
366
|
+
requestBody: {
|
|
367
|
+
required: true,
|
|
368
|
+
content: {
|
|
369
|
+
"application/json": {
|
|
370
|
+
schema: {
|
|
371
|
+
type: "object",
|
|
372
|
+
required: ["username", "password"],
|
|
373
|
+
properties: {
|
|
374
|
+
username: { type: "string" },
|
|
375
|
+
password: { type: "string", format: "password" },
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
responses: {
|
|
382
|
+
"200": { description: "Set-Cookie carries the signed session." },
|
|
383
|
+
"400": { description: "Missing username or password." },
|
|
384
|
+
"401": { description: "Invalid credentials." },
|
|
385
|
+
"429": { description: "Too many login attempts." },
|
|
386
|
+
"503": { description: "Server is in anonymous mode and does not accept logins." },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
"/api/auth/logout": {
|
|
391
|
+
post: {
|
|
392
|
+
tags: ["auth"],
|
|
393
|
+
summary: "Sign out — clears the session cookie.",
|
|
394
|
+
responses: { "204": { description: "Cookie cleared." } },
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
"/api/auth/oidc/login": {
|
|
398
|
+
get: {
|
|
399
|
+
tags: ["auth"],
|
|
400
|
+
summary: "Redirect to the configured OIDC identity provider's authorization endpoint.",
|
|
401
|
+
description: "Mounted only when OMCP_AUTH=oidc. Mints a short-lived flow cookie carrying state + nonce + PKCE code-verifier + return_to, then 302s the browser to the IdP.",
|
|
402
|
+
parameters: [
|
|
403
|
+
{ name: "return_to", in: "query", required: false, schema: { type: "string" }, description: "Same-origin path to redirect to after a successful callback. Absolute URLs or scheme-relative paths are rejected." },
|
|
404
|
+
],
|
|
405
|
+
responses: {
|
|
406
|
+
"302": { description: "Redirect to the IdP authorize_endpoint." },
|
|
407
|
+
"502": { description: "OIDC discovery failed (IdP unreachable / misconfigured)." },
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
"/api/auth/oidc/callback": {
|
|
412
|
+
get: {
|
|
413
|
+
tags: ["auth"],
|
|
414
|
+
summary: "OIDC code-flow callback — exchanges code for an id_token and mints an OMCP session cookie.",
|
|
415
|
+
description: "Verifies the state cookie, the IdP-returned state, the id_token signature (RS256/ES256), iss/aud/exp/nbf/nonce claims, then resolves OMCP roles from OMCP_OIDC_ROLES_CLAIM via OMCP_OIDC_ROLE_MAP and 302s to the cookie's return_to.",
|
|
416
|
+
parameters: [
|
|
417
|
+
{ name: "code", in: "query", schema: { type: "string" } },
|
|
418
|
+
{ name: "state", in: "query", schema: { type: "string" } },
|
|
419
|
+
{ name: "error", in: "query", required: false, schema: { type: "string" } },
|
|
420
|
+
],
|
|
421
|
+
responses: {
|
|
422
|
+
"302": { description: "Authentication succeeded; session cookie set and redirected to return_to." },
|
|
423
|
+
"400": { description: "Bad / expired / missing flow cookie, IdP error parameter present, or token exchange / verification failed." },
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
"/api/auth/oidc/logout": {
|
|
428
|
+
post: {
|
|
429
|
+
tags: ["auth"],
|
|
430
|
+
summary: "Sign out of the OMCP session. Does not perform RP-initiated logout against the IdP.",
|
|
431
|
+
description: "Clears the OMCP session cookie. To force an IdP-side sign-out, the UI should subsequently navigate to OMCP_OIDC_LOGOUT_REDIRECT (typically the IdP's end_session_endpoint).",
|
|
432
|
+
responses: { "204": { description: "Cookie cleared." } },
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
"/api/audit": {
|
|
436
|
+
get: {
|
|
437
|
+
tags: ["audit"],
|
|
438
|
+
summary: "Recent management-plane audit entries (most recent first).",
|
|
439
|
+
parameters: [
|
|
440
|
+
{ name: "from", in: "query", schema: { type: "string", format: "date-time" } },
|
|
441
|
+
{ name: "to", in: "query", schema: { type: "string", format: "date-time" } },
|
|
442
|
+
{ name: "actor", in: "query", schema: { type: "string" } },
|
|
443
|
+
{ name: "action", in: "query", schema: { type: "string" } },
|
|
444
|
+
{ name: "tenant", in: "query", schema: { type: "string" }, description: "Tenant scope. Non-admins are silently scoped to their own tenant; admins can pass any value (omit → all tenants)." },
|
|
445
|
+
{ name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 500, default: 100 } },
|
|
446
|
+
],
|
|
447
|
+
responses: {
|
|
448
|
+
"200": {
|
|
449
|
+
description: "Audit feed plus the chain's tip hash.",
|
|
450
|
+
content: {
|
|
451
|
+
"application/json": {
|
|
452
|
+
schema: {
|
|
453
|
+
type: "object",
|
|
454
|
+
properties: {
|
|
455
|
+
entries: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
456
|
+
tipHash: { type: "string" },
|
|
457
|
+
persisted: { type: "boolean" },
|
|
458
|
+
scopedTo: { type: ["string", "null"], description: "Tenant name this view is scoped to; null = all tenants (admin)." },
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
"401": { description: "Unauthenticated (basic mode)." },
|
|
465
|
+
"403": { description: "Missing audit:read permission." },
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
"/api/usage": {
|
|
470
|
+
get: {
|
|
471
|
+
tags: ["usage"],
|
|
472
|
+
summary: "Per-identity windowed call count for /mcp callers.",
|
|
473
|
+
parameters: [
|
|
474
|
+
{ name: "actor", in: "query", schema: { type: "string" }, description: "Narrow to a single identity." },
|
|
475
|
+
{ name: "tenant", in: "query", schema: { type: "string" }, description: "Tenant scope. Non-admins silently scoped to their own; admins can pick any (omit → all)." },
|
|
476
|
+
],
|
|
477
|
+
responses: {
|
|
478
|
+
"200": {
|
|
479
|
+
description: "Usage snapshot. Anonymous /mcp traffic does not appear here.",
|
|
480
|
+
content: {
|
|
481
|
+
"application/json": {
|
|
482
|
+
schema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {
|
|
485
|
+
identities: {
|
|
486
|
+
type: "array",
|
|
487
|
+
items: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
actor: { type: "string" },
|
|
491
|
+
tenant: { type: "string", description: "Tenant the identity belongs to. 'default' when single-tenant." },
|
|
492
|
+
count: { type: "integer" },
|
|
493
|
+
limit: { type: "integer" },
|
|
494
|
+
windowMs: { type: "integer" },
|
|
495
|
+
tokens: {
|
|
496
|
+
type: "object",
|
|
497
|
+
description: "Per-identity 24h-rolling token usage. `limit: 0` means uncapped (OMCP_TOOL_DAILY_TOKENS unset).",
|
|
498
|
+
properties: {
|
|
499
|
+
used: { type: "integer" },
|
|
500
|
+
limit: { type: "integer" },
|
|
501
|
+
windowMs: { type: "integer" },
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
defaultLimit: { type: "integer" },
|
|
508
|
+
windowMs: { type: "integer" },
|
|
509
|
+
tokens: {
|
|
510
|
+
type: "object",
|
|
511
|
+
description: "Process-wide defaults for the token-budget tracker.",
|
|
512
|
+
properties: {
|
|
513
|
+
defaultLimit: { type: "integer" },
|
|
514
|
+
windowMs: { type: "integer" },
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
"403": { description: "Missing audit:read permission." },
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
"/api/policy": {
|
|
527
|
+
get: {
|
|
528
|
+
tags: ["auth"],
|
|
529
|
+
summary: "Read-only view of the active RBAC policy (admin-only). Dry-run probe with ?resource=&action=&roles=[&tenant=].",
|
|
530
|
+
parameters: [
|
|
531
|
+
{ name: "roles", in: "query", required: false, schema: { type: "string" }, description: "Comma-separated role names to probe. Defaults to none (treated as anonymous → always denied)." },
|
|
532
|
+
{ name: "resource", in: "query", required: false, schema: { type: "string" }, description: "Resource to probe. Pair with `action` to enter dry-run mode." },
|
|
533
|
+
{ name: "action", in: "query", required: false, schema: { type: "string" }, description: "Action to probe. Pair with `resource` to enter dry-run mode." },
|
|
534
|
+
{ name: "tenant", in: "query", required: false, schema: { type: "string" }, description: "Tenant to probe under (dry-run only). Defaults to the caller's session tenant; admins may override to probe verdicts for any tenant — exactly how to debug tenant-conditional Rego rules under the OPA engine." },
|
|
535
|
+
],
|
|
536
|
+
responses: {
|
|
537
|
+
"200": {
|
|
538
|
+
description: "Either the full policy map (no probe params) or a dry-run decision (with `resource` + `action`).",
|
|
539
|
+
content: {
|
|
540
|
+
"application/json": {
|
|
541
|
+
schema: {
|
|
542
|
+
type: "object",
|
|
543
|
+
properties: {
|
|
544
|
+
engine: { type: "string", description: "Identifier of the active engine: 'builtin', 'file:<path>', 'opa:<url>'." },
|
|
545
|
+
tenantAware: { type: "boolean", description: "True when the active engine honours session.tenant on .evaluate() — i.e. OPA. Built-in / file-loaded engines ignore tenant ctx (false)." },
|
|
546
|
+
policy: { type: "object", additionalProperties: true },
|
|
547
|
+
roles: { type: "array", items: { type: "string" } },
|
|
548
|
+
note: { type: "string" },
|
|
549
|
+
dryRun: {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties: {
|
|
552
|
+
roles: { type: "array", items: { type: "string" } },
|
|
553
|
+
resource: { type: "string" },
|
|
554
|
+
action: { type: "string" },
|
|
555
|
+
tenant: { type: "string", description: "Tenant the probe ran under (echoed from ?tenant= or the caller's session)." },
|
|
556
|
+
allowed: { type: "boolean" },
|
|
557
|
+
reason: { type: "string" },
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
"403": { description: "Missing users:delete permission (admin-only)." },
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
"/api/policy/roles/{name}": {
|
|
570
|
+
put: {
|
|
571
|
+
tags: ["auth"],
|
|
572
|
+
summary: "Upsert a role in the file-backed RBAC policy (admin-only, file engine only).",
|
|
573
|
+
description: "Writes through OMCP_RBAC_POLICY_FILE atomically. 409 if the active engine isn't `file:…` or the env var is unset. Permissions are validated against VALID_RESOURCES + VALID_ACTIONS; unknown values return 422 with code OMCP_POLICY_UNKNOWN_RESOURCE / OMCP_POLICY_UNKNOWN_ACTION. On success the in-memory PolicyEngine is hot-swapped — existing gates pick up the new policy without a server restart.",
|
|
574
|
+
parameters: [
|
|
575
|
+
{ name: "name", in: "path", required: true, schema: { type: "string" } },
|
|
576
|
+
],
|
|
577
|
+
requestBody: {
|
|
578
|
+
required: true,
|
|
579
|
+
content: { "application/json": { schema: {
|
|
580
|
+
type: "object",
|
|
581
|
+
required: ["permissions"],
|
|
582
|
+
properties: { permissions: { type: "array", items: { type: "object", properties: {
|
|
583
|
+
resource: { type: "string" },
|
|
584
|
+
action: { type: "string" },
|
|
585
|
+
} } } },
|
|
586
|
+
} } },
|
|
587
|
+
},
|
|
588
|
+
responses: {
|
|
589
|
+
"200": { description: "Role saved + hot-swapped." },
|
|
590
|
+
"400": { description: "Body shape invalid OR role name pattern rejected." },
|
|
591
|
+
"403": { description: "Missing users:delete permission (admin-only)." },
|
|
592
|
+
"409": { description: "Active engine isn't `file:…`, or OMCP_RBAC_POLICY_FILE unset (codes: OMCP_POLICY_ENGINE_NOT_FILE / OMCP_POLICY_FILE_NOT_SET)." },
|
|
593
|
+
"422": { description: "Unknown resource or action (codes: OMCP_POLICY_UNKNOWN_RESOURCE / OMCP_POLICY_UNKNOWN_ACTION)." },
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
"/api/users/{username}/roles": {
|
|
598
|
+
put: {
|
|
599
|
+
tags: ["auth"],
|
|
600
|
+
summary: "Update a local user's role assignments (admin-only, file-backed).",
|
|
601
|
+
description: "Writes through OMCP_USERS_FILE. Roles are validated against the active policy engine's role catalogue; unknown role names return 422 with OMCP_USER_UNKNOWN_ROLE. The in-memory user store is refreshed atomically after the file write so the next login picks up the new roles without a server restart.",
|
|
602
|
+
parameters: [
|
|
603
|
+
{ name: "username", in: "path", required: true, schema: { type: "string" } },
|
|
604
|
+
],
|
|
605
|
+
requestBody: {
|
|
606
|
+
required: true,
|
|
607
|
+
content: { "application/json": { schema: {
|
|
608
|
+
type: "object",
|
|
609
|
+
required: ["roles"],
|
|
610
|
+
properties: { roles: { type: "array", items: { type: "string" } } },
|
|
611
|
+
} } },
|
|
612
|
+
},
|
|
613
|
+
responses: {
|
|
614
|
+
"200": { description: "Roles updated." },
|
|
615
|
+
"400": { description: "Body must be { roles: string[] }." },
|
|
616
|
+
"403": { description: "Missing users:delete permission (admin-only)." },
|
|
617
|
+
"404": { description: "User not found OR users file unreadable." },
|
|
618
|
+
"409": { description: "OMCP_USERS_FILE is not configured — basic-mode user roles can't be edited via the API." },
|
|
619
|
+
"422": { description: "tools[] references unknown role names. Body includes `unknown` + `available`; error code OMCP_USER_UNKNOWN_ROLE." },
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
"/api/subjects": {
|
|
624
|
+
get: {
|
|
625
|
+
tags: ["auth"],
|
|
626
|
+
summary: "Aggregated subjects view — local users + API-key names + OIDC group mappings (admin-only).",
|
|
627
|
+
description: "Read-only catalogue of the principals an OMCP deployment knows about. Three independent sources: OMCP_USERS_FILE (users), OMCP_API_KEYS (apiKeys), OMCP_OIDC_ROLE_MAP (oidcGroups). Tokens + password hashes are never returned — only metadata.",
|
|
628
|
+
responses: {
|
|
629
|
+
"200": {
|
|
630
|
+
description: "Subjects payload.",
|
|
631
|
+
content: { "application/json": { schema: {
|
|
632
|
+
type: "object",
|
|
633
|
+
properties: {
|
|
634
|
+
users: { type: "array", items: { type: "object", properties: {
|
|
635
|
+
username: { type: "string" }, name: { type: "string" },
|
|
636
|
+
roles: { type: "array", items: { type: "string" } },
|
|
637
|
+
tenant: { type: "string" },
|
|
638
|
+
} } },
|
|
639
|
+
apiKeys: { type: "array", items: { type: "object", properties: {
|
|
640
|
+
name: { type: "string" }, tenant: { type: "string" },
|
|
641
|
+
productId: { type: "string" },
|
|
642
|
+
bypassRedaction: { type: "boolean" },
|
|
643
|
+
allowedSources: { type: "array", items: { type: "string" } },
|
|
644
|
+
} } },
|
|
645
|
+
oidcGroups: { type: "array", items: { type: "object", properties: {
|
|
646
|
+
claim: { type: "string" }, role: { type: "string" },
|
|
647
|
+
} } },
|
|
648
|
+
sources: { type: "object", properties: {
|
|
649
|
+
users: { type: ["string", "null"] },
|
|
650
|
+
apiKeys: { type: ["string", "null"] },
|
|
651
|
+
oidcGroups: { type: ["string", "null"] },
|
|
652
|
+
} },
|
|
653
|
+
},
|
|
654
|
+
} } },
|
|
655
|
+
},
|
|
656
|
+
"403": { description: "Missing users:delete permission (admin-only)." },
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
"/api/products": {
|
|
661
|
+
get: {
|
|
662
|
+
tags: ["products"],
|
|
663
|
+
summary: "Loaded MCP Products catalogue (curated tool bundles for agents).",
|
|
664
|
+
parameters: [
|
|
665
|
+
{ name: "tenant", in: "query", schema: { type: "string" }, description: "Tenant scope. Non-admins silently scoped to their own; admins can pick any (omit → all)." },
|
|
666
|
+
],
|
|
667
|
+
responses: {
|
|
668
|
+
"200": {
|
|
669
|
+
description: "Products list scoped to caller's tenant; admins see staging entries too.",
|
|
670
|
+
content: {
|
|
671
|
+
"application/json": {
|
|
672
|
+
schema: {
|
|
673
|
+
type: "object",
|
|
674
|
+
properties: {
|
|
675
|
+
products: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
676
|
+
configured: { type: "boolean" },
|
|
677
|
+
scopedTo: { type: ["string", "null"] },
|
|
678
|
+
includesStaging: { type: "boolean" },
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
"403": { description: "Missing products:read permission." },
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
post: {
|
|
688
|
+
tags: ["products"],
|
|
689
|
+
summary: "Create a new product (strict create — 409 on conflict; PUT for upsert).",
|
|
690
|
+
description: "Strict create-only variant of PUT. Same tenancy + typo-guard posture. Returns 409 when a product with body.id already exists. Body must include id + name; tools[] entries must reference registered MCP tool names (typo → 422).",
|
|
691
|
+
requestBody: {
|
|
692
|
+
required: true,
|
|
693
|
+
content: { "application/json": { schema: { type: "object", additionalProperties: true } } },
|
|
694
|
+
},
|
|
695
|
+
responses: {
|
|
696
|
+
"201": { description: "Created.", content: { "application/json": { schema: { type: "object", properties: {
|
|
697
|
+
product: { type: "object", additionalProperties: true },
|
|
698
|
+
persisted: { type: "boolean" },
|
|
699
|
+
} } } } },
|
|
700
|
+
"400": { description: "Body invalid (missing id / shape rejected by validateProduct)." },
|
|
701
|
+
"403": { description: "Missing products:write permission, or non-admin attempting to create in another tenant." },
|
|
702
|
+
"409": { description: "Product with that id already exists — use PUT to update." },
|
|
703
|
+
"422": { description: "tools[] references unknown tool names. Body includes `unknown` + `available`; error code OMCP_PRODUCT_UNKNOWN_TOOL." },
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
"/api/products/{id}": {
|
|
708
|
+
get: {
|
|
709
|
+
tags: ["products"],
|
|
710
|
+
summary: "Single product by id (404 on cross-tenant or staging probe by non-admin).",
|
|
711
|
+
parameters: [
|
|
712
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
713
|
+
],
|
|
714
|
+
responses: {
|
|
715
|
+
"200": { description: "The product entry.", content: { "application/json": { schema: { type: "object", additionalProperties: true } } } },
|
|
716
|
+
"404": { description: "Not found (or hidden by tenant / staging scope)." },
|
|
717
|
+
"403": { description: "Missing products:read permission." },
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
put: {
|
|
721
|
+
tags: ["products"],
|
|
722
|
+
summary: "Upsert a product (admin + operator). Body must match the OMCP_PRODUCTS_FILE entry shape.",
|
|
723
|
+
parameters: [
|
|
724
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
725
|
+
],
|
|
726
|
+
requestBody: {
|
|
727
|
+
required: true,
|
|
728
|
+
content: { "application/json": { schema: { type: "object", additionalProperties: true } } },
|
|
729
|
+
},
|
|
730
|
+
responses: {
|
|
731
|
+
"200": {
|
|
732
|
+
description: "Upsert succeeded; returns the validated product + a persisted flag.",
|
|
733
|
+
content: { "application/json": { schema: { type: "object", properties: {
|
|
734
|
+
product: { type: "object", additionalProperties: true },
|
|
735
|
+
persisted: { type: "boolean", description: "True when OMCP_PRODUCTS_FILE was set and the file was rewritten." },
|
|
736
|
+
} } } },
|
|
737
|
+
},
|
|
738
|
+
"400": { description: "Body shape invalid (validateProduct rejected — typo, unknown key, wrong type, ...)." },
|
|
739
|
+
"403": { description: "Missing products:write permission, or non-admin attempting to write into another tenant." },
|
|
740
|
+
"404": { description: "Existing product belongs to a different tenant (non-admin)." },
|
|
741
|
+
"422": { description: "tools[] references unknown tool names. Body includes `unknown` + `available`; error code OMCP_PRODUCT_UNKNOWN_TOOL." },
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
delete: {
|
|
745
|
+
tags: ["products"],
|
|
746
|
+
summary: "Delete a product by id (admin only).",
|
|
747
|
+
parameters: [
|
|
748
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
749
|
+
],
|
|
750
|
+
responses: {
|
|
751
|
+
"204": { description: "Deleted." },
|
|
752
|
+
"403": { description: "Missing products:delete permission." },
|
|
753
|
+
"404": { description: "Not found (or hidden by tenant scope)." },
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
"/api/products/{id}/preview": {
|
|
758
|
+
get: {
|
|
759
|
+
tags: ["products"],
|
|
760
|
+
summary: "Agent preview — the filtered tools/list a credential bound to this product would receive.",
|
|
761
|
+
description: "Same tenancy + staging filter as GET /api/products/{id}. Returns the product's branding/identity metadata + the registered MCP tools after applying its tools allow-list. The UI uses this for the per-card 'Preview as agent' affordance.",
|
|
762
|
+
parameters: [
|
|
763
|
+
{ name: "id", in: "path", required: true, schema: { type: "string" } },
|
|
764
|
+
],
|
|
765
|
+
responses: {
|
|
766
|
+
"200": {
|
|
767
|
+
description: "Preview payload.",
|
|
768
|
+
content: { "application/json": { schema: {
|
|
769
|
+
type: "object",
|
|
770
|
+
required: ["product", "unrestricted", "tools"],
|
|
771
|
+
properties: {
|
|
772
|
+
product: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {
|
|
775
|
+
id: { type: "string" },
|
|
776
|
+
name: { type: "string" },
|
|
777
|
+
version: { type: "string" },
|
|
778
|
+
branding: { type: "object", additionalProperties: true },
|
|
779
|
+
tenant: { type: "string" },
|
|
780
|
+
status: { type: "string" },
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
unrestricted: { type: "boolean", description: "True when the product has no tools allow-list — the bound agent sees every registered tool." },
|
|
784
|
+
tools: {
|
|
785
|
+
type: "array",
|
|
786
|
+
items: {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
name: { type: "string" },
|
|
790
|
+
category: { type: "string", enum: ["discovery", "query", "diagnose", "topology"] },
|
|
791
|
+
summary: { type: "string" },
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
} } },
|
|
797
|
+
},
|
|
798
|
+
"404": { description: "Not found (or hidden by tenant / staging scope)." },
|
|
799
|
+
"403": { description: "Missing products:read permission." },
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
"/api/catalog": {
|
|
804
|
+
get: {
|
|
805
|
+
tags: ["catalog"],
|
|
806
|
+
summary: "Loaded service catalog (owner / tier / on-call / SLO).",
|
|
807
|
+
parameters: [
|
|
808
|
+
{ name: "tenant", in: "query", schema: { type: "string" }, description: "Tenant scope. Non-admins silently scoped to their own; admins can pick any (omit → all)." },
|
|
809
|
+
],
|
|
810
|
+
responses: {
|
|
811
|
+
"200": {
|
|
812
|
+
description: "Catalog map keyed by service name.",
|
|
813
|
+
content: {
|
|
814
|
+
"application/json": {
|
|
815
|
+
schema: {
|
|
816
|
+
type: "object",
|
|
817
|
+
properties: {
|
|
818
|
+
services: { type: "object", additionalProperties: true },
|
|
819
|
+
count: { type: "integer" },
|
|
820
|
+
configured: { type: "boolean", description: "true when OMCP_SERVICE_CATALOG_FILE is set." },
|
|
821
|
+
scopedTo: { type: ["string", "null"], description: "Tenant name this view is scoped to; null = all tenants (admin)." },
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
"403": { description: "Missing catalog:read permission." },
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
},
|
|
183
831
|
},
|
|
184
832
|
};
|
|
185
833
|
return doc;
|