@thotischner/observability-mcp 1.7.1 → 1.8.1
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/audit/log.d.ts +99 -0
- package/dist/audit/log.js +180 -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/auth/credentials.d.ts +18 -0
- package/dist/auth/credentials.js +26 -1
- package/dist/auth/credentials.test.js +26 -1
- package/dist/auth/local-users.d.ts +62 -0
- package/dist/auth/local-users.js +143 -0
- package/dist/auth/local-users.test.d.ts +1 -0
- package/dist/auth/local-users.test.js +80 -0
- package/dist/auth/middleware.d.ts +48 -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/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 +124 -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/runtime.d.ts +63 -0
- package/dist/auth/oidc/runtime.js +129 -0
- package/dist/auth/oidc/runtime.test.d.ts +1 -0
- package/dist/auth/oidc/runtime.test.js +180 -0
- package/dist/auth/policy/engine.d.ts +48 -0
- package/dist/auth/policy/engine.js +73 -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 +35 -0
- package/dist/auth/policy/loader.js +100 -0
- package/dist/auth/policy/opa.d.ts +69 -0
- package/dist/auth/policy/opa.js +162 -0
- package/dist/auth/policy/opa.test.d.ts +1 -0
- package/dist/auth/policy/opa.test.js +158 -0
- package/dist/auth/rbac.d.ts +40 -0
- package/dist/auth/rbac.js +120 -0
- package/dist/auth/rbac.test.d.ts +1 -0
- package/dist/auth/rbac.test.js +121 -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/context.d.ts +13 -1
- package/dist/context.js +5 -1
- package/dist/index.js +1012 -29
- package/dist/net/egress-policy.js +2 -0
- package/dist/openapi.js +440 -0
- package/dist/openapi.test.d.ts +1 -0
- package/dist/openapi.test.js +64 -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/products/loader.d.ts +84 -0
- package/dist/products/loader.js +216 -0
- package/dist/products/loader.test.d.ts +1 -0
- package/dist/products/loader.test.js +168 -0
- package/dist/quota/limiter.d.ts +72 -0
- package/dist/quota/limiter.js +105 -0
- package/dist/quota/limiter.test.d.ts +1 -0
- package/dist/quota/limiter.test.js +119 -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/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/ui/index.html +1454 -88
- package/package.json +20 -3
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { readCatalogFile, validateCatalog, CatalogStore } from "./loader.js";
|
|
7
|
+
test("readCatalogFile — missing path returns empty catalog", async () => {
|
|
8
|
+
const c = await readCatalogFile(undefined);
|
|
9
|
+
assert.deepEqual(c, { services: {} });
|
|
10
|
+
});
|
|
11
|
+
test("readCatalogFile — missing file returns empty catalog (no crash)", async () => {
|
|
12
|
+
const c = await readCatalogFile("/tmp/definitely-does-not-exist-omcp.json");
|
|
13
|
+
assert.deepEqual(c, { services: {} });
|
|
14
|
+
});
|
|
15
|
+
test("readCatalogFile — malformed JSON returns empty catalog", async () => {
|
|
16
|
+
const dir = await mkdtemp(join(tmpdir(), "omcp-catalog-"));
|
|
17
|
+
try {
|
|
18
|
+
const file = join(dir, "catalog.json");
|
|
19
|
+
await writeFile(file, "{this is not json", "utf8");
|
|
20
|
+
const c = await readCatalogFile(file);
|
|
21
|
+
assert.deepEqual(c, { services: {} });
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await rm(dir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
test("readCatalogFile — valid file returns parsed catalog", async () => {
|
|
28
|
+
const dir = await mkdtemp(join(tmpdir(), "omcp-catalog-"));
|
|
29
|
+
try {
|
|
30
|
+
const file = join(dir, "catalog.json");
|
|
31
|
+
await writeFile(file, JSON.stringify({
|
|
32
|
+
services: {
|
|
33
|
+
"payment-service": {
|
|
34
|
+
owner: "team-payments",
|
|
35
|
+
tier: "tier-1",
|
|
36
|
+
dataClassification: "confidential",
|
|
37
|
+
slo: "99.9%",
|
|
38
|
+
runbooks: ["https://runbooks.example/payments"],
|
|
39
|
+
tags: ["pci", "regulated"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}), "utf8");
|
|
43
|
+
const c = await readCatalogFile(file);
|
|
44
|
+
assert.equal(c.services["payment-service"].owner, "team-payments");
|
|
45
|
+
assert.equal(c.services["payment-service"].tier, "tier-1");
|
|
46
|
+
assert.deepEqual(c.services["payment-service"].tags, ["pci", "regulated"]);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await rm(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
test("validateCatalog — rejects invalid tier values", () => {
|
|
53
|
+
const c = validateCatalog({ services: { svc: { tier: "tier-9" } } });
|
|
54
|
+
assert.equal(c.services.svc.tier, undefined);
|
|
55
|
+
});
|
|
56
|
+
test("validateCatalog — rejects invalid data classification", () => {
|
|
57
|
+
const c = validateCatalog({ services: { svc: { dataClassification: "ultra-secret" } } });
|
|
58
|
+
assert.equal(c.services.svc.dataClassification, undefined);
|
|
59
|
+
});
|
|
60
|
+
test("validateCatalog — strips non-string entries from runbooks / tags", () => {
|
|
61
|
+
const c = validateCatalog({
|
|
62
|
+
services: { svc: { runbooks: ["a", 1, "b", null], tags: [{}, "tag1"] } },
|
|
63
|
+
});
|
|
64
|
+
assert.deepEqual(c.services.svc.runbooks, ["a", "b"]);
|
|
65
|
+
assert.deepEqual(c.services.svc.tags, ["tag1"]);
|
|
66
|
+
});
|
|
67
|
+
test("validateCatalog — skips non-object service entries", () => {
|
|
68
|
+
const c = validateCatalog({ services: { svc1: "string", svc2: null, svc3: { owner: "team" } } });
|
|
69
|
+
assert.equal(c.services.svc1, undefined);
|
|
70
|
+
assert.equal(c.services.svc2, undefined);
|
|
71
|
+
assert.equal(c.services.svc3.owner, "team");
|
|
72
|
+
});
|
|
73
|
+
test("CatalogStore — get / list / count / replace", () => {
|
|
74
|
+
const store = new CatalogStore({
|
|
75
|
+
services: {
|
|
76
|
+
a: { owner: "team-a" },
|
|
77
|
+
b: { owner: "team-b" },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
assert.equal(store.count(), 2);
|
|
81
|
+
assert.equal(store.get("a")?.owner, "team-a");
|
|
82
|
+
assert.equal(store.get("nope"), undefined);
|
|
83
|
+
assert.equal(Object.keys(store.list()).length, 2);
|
|
84
|
+
store.replace({ services: { x: { owner: "team-x" } } });
|
|
85
|
+
assert.equal(store.count(), 1);
|
|
86
|
+
assert.equal(store.get("x")?.owner, "team-x");
|
|
87
|
+
});
|
|
88
|
+
test("CatalogStore — tenant filter scopes get / list / count", () => {
|
|
89
|
+
const store = new CatalogStore({
|
|
90
|
+
services: {
|
|
91
|
+
"acme-payments": { owner: "team-a", tenant: "acme" },
|
|
92
|
+
"bigco-payments": { owner: "team-b", tenant: "bigco" },
|
|
93
|
+
"shared-cdn": { owner: "team-c" }, // no tenant → "default"
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// No tenant filter → see everything (admin-style read)
|
|
97
|
+
assert.equal(store.count(), 3);
|
|
98
|
+
// Tenant-scoped: each sees its own entries + nothing else
|
|
99
|
+
assert.equal(store.count("acme"), 1);
|
|
100
|
+
assert.equal(store.get("acme-payments", "acme")?.owner, "team-a");
|
|
101
|
+
assert.equal(store.get("bigco-payments", "acme"), undefined, "cross-tenant get must return undefined");
|
|
102
|
+
assert.equal(Object.keys(store.list("acme"))[0], "acme-payments");
|
|
103
|
+
// Default tenant includes entries with no tenant field
|
|
104
|
+
assert.equal(store.count("default"), 1);
|
|
105
|
+
assert.equal(store.get("shared-cdn", "default")?.owner, "team-c");
|
|
106
|
+
// Unknown tenant → empty
|
|
107
|
+
assert.equal(store.count("unknown"), 0);
|
|
108
|
+
});
|
package/dist/context.d.ts
CHANGED
|
@@ -18,10 +18,22 @@ export interface RequestContext {
|
|
|
18
18
|
* scoping (tools/services/lookback/read-only) is a separate concern.
|
|
19
19
|
*/
|
|
20
20
|
allowedSources?: string[];
|
|
21
|
+
/** When true, the credential is allowed to opt out of redaction on a
|
|
22
|
+
* per-tool-call basis. The actual bypass is engaged only when the
|
|
23
|
+
* tool call ALSO sets `bypass_redaction: true` in its args. Default
|
|
24
|
+
* false. Configured via OMCP_KEY_BYPASS_REDACTION. */
|
|
25
|
+
allowBypassRedaction?: boolean;
|
|
26
|
+
/** Tenant the request operates in. ALWAYS set — defaults to
|
|
27
|
+
* "default" for anonymous principals + missing-tenant credentials,
|
|
28
|
+
* preserving the single-namespace behaviour of pre-E7 deployments. */
|
|
29
|
+
tenant: string;
|
|
21
30
|
/** Correlates all tool calls within one transport request/session. */
|
|
22
31
|
correlationId: string;
|
|
23
32
|
}
|
|
24
33
|
/** Default all-access anonymous context — preserves current behaviour. */
|
|
25
34
|
export declare function defaultContext(): RequestContext;
|
|
26
35
|
/** Context for an authenticated API-key principal. */
|
|
27
|
-
export declare function principalContext(principalId: string, allowedSources?: string[]
|
|
36
|
+
export declare function principalContext(principalId: string, allowedSources?: string[], opts?: {
|
|
37
|
+
allowBypassRedaction?: boolean;
|
|
38
|
+
tenant?: string;
|
|
39
|
+
}): RequestContext;
|
package/dist/context.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { DEFAULT_TENANT, normaliseTenant } from "./tenancy/context.js";
|
|
2
3
|
/** Default all-access anonymous context — preserves current behaviour. */
|
|
3
4
|
export function defaultContext() {
|
|
4
5
|
return {
|
|
5
6
|
principalId: "anonymous",
|
|
6
7
|
auth: "anonymous",
|
|
8
|
+
tenant: DEFAULT_TENANT,
|
|
7
9
|
correlationId: randomUUID(),
|
|
8
10
|
};
|
|
9
11
|
}
|
|
10
12
|
/** Context for an authenticated API-key principal. */
|
|
11
|
-
export function principalContext(principalId, allowedSources) {
|
|
13
|
+
export function principalContext(principalId, allowedSources, opts = {}) {
|
|
12
14
|
return {
|
|
13
15
|
principalId,
|
|
14
16
|
auth: "apikey",
|
|
15
17
|
allowedSources: allowedSources && allowedSources.length > 0 ? allowedSources : undefined,
|
|
18
|
+
allowBypassRedaction: opts.allowBypassRedaction || undefined,
|
|
19
|
+
tenant: normaliseTenant(opts.tenant),
|
|
16
20
|
correlationId: randomUUID(),
|
|
17
21
|
};
|
|
18
22
|
}
|