@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.
Files changed (105) hide show
  1. package/config/products.yaml.example +48 -0
  2. package/dist/audit/log.d.ts +99 -0
  3. package/dist/audit/log.js +180 -0
  4. package/dist/audit/log.test.d.ts +1 -0
  5. package/dist/audit/log.test.js +147 -0
  6. package/dist/audit/middleware.d.ts +20 -0
  7. package/dist/audit/middleware.js +50 -0
  8. package/dist/auth/credentials.d.ts +18 -0
  9. package/dist/auth/credentials.js +26 -1
  10. package/dist/auth/credentials.test.js +26 -1
  11. package/dist/auth/local-users.d.ts +62 -0
  12. package/dist/auth/local-users.js +143 -0
  13. package/dist/auth/local-users.test.d.ts +1 -0
  14. package/dist/auth/local-users.test.js +80 -0
  15. package/dist/auth/middleware.d.ts +48 -0
  16. package/dist/auth/middleware.js +65 -0
  17. package/dist/auth/middleware.test.d.ts +1 -0
  18. package/dist/auth/middleware.test.js +90 -0
  19. package/dist/auth/oidc/client.d.ts +73 -0
  20. package/dist/auth/oidc/client.js +104 -0
  21. package/dist/auth/oidc/client.test.d.ts +1 -0
  22. package/dist/auth/oidc/client.test.js +121 -0
  23. package/dist/auth/oidc/discovery.d.ts +38 -0
  24. package/dist/auth/oidc/discovery.js +48 -0
  25. package/dist/auth/oidc/discovery.test.d.ts +1 -0
  26. package/dist/auth/oidc/discovery.test.js +68 -0
  27. package/dist/auth/oidc/endpoints.d.ts +20 -0
  28. package/dist/auth/oidc/endpoints.js +124 -0
  29. package/dist/auth/oidc/endpoints.test.d.ts +7 -0
  30. package/dist/auth/oidc/endpoints.test.js +304 -0
  31. package/dist/auth/oidc/flow-cookie.d.ts +57 -0
  32. package/dist/auth/oidc/flow-cookie.js +142 -0
  33. package/dist/auth/oidc/flow-cookie.test.d.ts +1 -0
  34. package/dist/auth/oidc/flow-cookie.test.js +0 -0
  35. package/dist/auth/oidc/index.d.ts +7 -0
  36. package/dist/auth/oidc/index.js +6 -0
  37. package/dist/auth/oidc/jwks.d.ts +36 -0
  38. package/dist/auth/oidc/jwks.js +69 -0
  39. package/dist/auth/oidc/jwks.test.d.ts +1 -0
  40. package/dist/auth/oidc/jwks.test.js +65 -0
  41. package/dist/auth/oidc/jwt.d.ts +62 -0
  42. package/dist/auth/oidc/jwt.js +113 -0
  43. package/dist/auth/oidc/jwt.test.d.ts +1 -0
  44. package/dist/auth/oidc/jwt.test.js +141 -0
  45. package/dist/auth/oidc/pkce.d.ts +19 -0
  46. package/dist/auth/oidc/pkce.js +43 -0
  47. package/dist/auth/oidc/pkce.test.d.ts +1 -0
  48. package/dist/auth/oidc/pkce.test.js +55 -0
  49. package/dist/auth/oidc/runtime.d.ts +63 -0
  50. package/dist/auth/oidc/runtime.js +129 -0
  51. package/dist/auth/oidc/runtime.test.d.ts +1 -0
  52. package/dist/auth/oidc/runtime.test.js +180 -0
  53. package/dist/auth/policy/engine.d.ts +48 -0
  54. package/dist/auth/policy/engine.js +73 -0
  55. package/dist/auth/policy/engine.test.d.ts +1 -0
  56. package/dist/auth/policy/engine.test.js +98 -0
  57. package/dist/auth/policy/loader.d.ts +35 -0
  58. package/dist/auth/policy/loader.js +100 -0
  59. package/dist/auth/policy/opa.d.ts +69 -0
  60. package/dist/auth/policy/opa.js +162 -0
  61. package/dist/auth/policy/opa.test.d.ts +1 -0
  62. package/dist/auth/policy/opa.test.js +158 -0
  63. package/dist/auth/rbac.d.ts +40 -0
  64. package/dist/auth/rbac.js +120 -0
  65. package/dist/auth/rbac.test.d.ts +1 -0
  66. package/dist/auth/rbac.test.js +121 -0
  67. package/dist/auth/session.d.ts +66 -0
  68. package/dist/auth/session.js +146 -0
  69. package/dist/auth/session.test.d.ts +1 -0
  70. package/dist/auth/session.test.js +90 -0
  71. package/dist/catalog/loader.d.ts +67 -0
  72. package/dist/catalog/loader.js +122 -0
  73. package/dist/catalog/loader.test.d.ts +1 -0
  74. package/dist/catalog/loader.test.js +108 -0
  75. package/dist/context.d.ts +13 -1
  76. package/dist/context.js +5 -1
  77. package/dist/index.js +1012 -29
  78. package/dist/net/egress-policy.js +2 -0
  79. package/dist/openapi.js +440 -0
  80. package/dist/openapi.test.d.ts +1 -0
  81. package/dist/openapi.test.js +64 -0
  82. package/dist/policy/redact.d.ts +44 -0
  83. package/dist/policy/redact.js +144 -0
  84. package/dist/policy/redact.test.d.ts +1 -0
  85. package/dist/policy/redact.test.js +172 -0
  86. package/dist/products/loader.d.ts +84 -0
  87. package/dist/products/loader.js +216 -0
  88. package/dist/products/loader.test.d.ts +1 -0
  89. package/dist/products/loader.test.js +168 -0
  90. package/dist/quota/limiter.d.ts +72 -0
  91. package/dist/quota/limiter.js +105 -0
  92. package/dist/quota/limiter.test.d.ts +1 -0
  93. package/dist/quota/limiter.test.js +119 -0
  94. package/dist/quota/token-budget.d.ts +119 -0
  95. package/dist/quota/token-budget.js +297 -0
  96. package/dist/quota/token-budget.test.d.ts +1 -0
  97. package/dist/quota/token-budget.test.js +215 -0
  98. package/dist/tenancy/context.d.ts +45 -0
  99. package/dist/tenancy/context.js +97 -0
  100. package/dist/tenancy/context.test.d.ts +1 -0
  101. package/dist/tenancy/context.test.js +72 -0
  102. package/dist/tenancy/migration.test.d.ts +7 -0
  103. package/dist/tenancy/migration.test.js +75 -0
  104. package/dist/ui/index.html +1454 -88
  105. 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[]): RequestContext;
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
  }