@weaveaijs/mcp-observatory 0.3.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.
Files changed (92) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +322 -0
  3. package/dist/core/context.d.ts +46 -0
  4. package/dist/core/context.d.ts.map +1 -0
  5. package/dist/core/context.js +38 -0
  6. package/dist/core/context.js.map +1 -0
  7. package/dist/core/tracer.d.ts +17 -0
  8. package/dist/core/tracer.d.ts.map +1 -0
  9. package/dist/core/tracer.js +29 -0
  10. package/dist/core/tracer.js.map +1 -0
  11. package/dist/core/wrapper.d.ts +34 -0
  12. package/dist/core/wrapper.d.ts.map +1 -0
  13. package/dist/core/wrapper.js +93 -0
  14. package/dist/core/wrapper.js.map +1 -0
  15. package/dist/cost/pricing.d.ts +6 -0
  16. package/dist/cost/pricing.d.ts.map +1 -0
  17. package/dist/cost/pricing.js +21 -0
  18. package/dist/cost/pricing.js.map +1 -0
  19. package/dist/cost/tokenizer.d.ts +7 -0
  20. package/dist/cost/tokenizer.d.ts.map +1 -0
  21. package/dist/cost/tokenizer.js +25 -0
  22. package/dist/cost/tokenizer.js.map +1 -0
  23. package/dist/demo/client.d.ts +18 -0
  24. package/dist/demo/client.d.ts.map +1 -0
  25. package/dist/demo/client.js +57 -0
  26. package/dist/demo/client.js.map +1 -0
  27. package/dist/demo/runDemo.d.ts +2 -0
  28. package/dist/demo/runDemo.d.ts.map +1 -0
  29. package/dist/demo/runDemo.js +15 -0
  30. package/dist/demo/runDemo.js.map +1 -0
  31. package/dist/demo/server.d.ts +19 -0
  32. package/dist/demo/server.d.ts.map +1 -0
  33. package/dist/demo/server.js +100 -0
  34. package/dist/demo/server.js.map +1 -0
  35. package/dist/hallucination/scoring.d.ts +14 -0
  36. package/dist/hallucination/scoring.d.ts.map +1 -0
  37. package/dist/hallucination/scoring.js +67 -0
  38. package/dist/hallucination/scoring.js.map +1 -0
  39. package/dist/index.d.ts +20 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +23 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/proposal/proposer.d.ts +30 -0
  44. package/dist/proposal/proposer.d.ts.map +1 -0
  45. package/dist/proposal/proposer.js +73 -0
  46. package/dist/proposal/proposer.js.map +1 -0
  47. package/dist/proposal/storage.d.ts +38 -0
  48. package/dist/proposal/storage.d.ts.map +1 -0
  49. package/dist/proposal/storage.js +115 -0
  50. package/dist/proposal/storage.js.map +1 -0
  51. package/dist/proposal/token.d.ts +34 -0
  52. package/dist/proposal/token.d.ts.map +1 -0
  53. package/dist/proposal/token.js +52 -0
  54. package/dist/proposal/token.js.map +1 -0
  55. package/dist/proposal/verifier.d.ts +20 -0
  56. package/dist/proposal/verifier.d.ts.map +1 -0
  57. package/dist/proposal/verifier.js +34 -0
  58. package/dist/proposal/verifier.js.map +1 -0
  59. package/dist/risk/scoring.d.ts +11 -0
  60. package/dist/risk/scoring.d.ts.map +1 -0
  61. package/dist/risk/scoring.js +54 -0
  62. package/dist/risk/scoring.js.map +1 -0
  63. package/dist/tests/hashing.test.d.ts +2 -0
  64. package/dist/tests/hashing.test.d.ts.map +1 -0
  65. package/dist/tests/hashing.test.js +39 -0
  66. package/dist/tests/hashing.test.js.map +1 -0
  67. package/dist/tests/proposer.test.d.ts +2 -0
  68. package/dist/tests/proposer.test.d.ts.map +1 -0
  69. package/dist/tests/proposer.test.js +49 -0
  70. package/dist/tests/proposer.test.js.map +1 -0
  71. package/dist/tests/scoring.test.d.ts +2 -0
  72. package/dist/tests/scoring.test.d.ts.map +1 -0
  73. package/dist/tests/scoring.test.js +41 -0
  74. package/dist/tests/scoring.test.js.map +1 -0
  75. package/dist/tests/token.test.d.ts +2 -0
  76. package/dist/tests/token.test.d.ts.map +1 -0
  77. package/dist/tests/token.test.js +51 -0
  78. package/dist/tests/token.test.js.map +1 -0
  79. package/dist/tests/tracer.test.d.ts +2 -0
  80. package/dist/tests/tracer.test.d.ts.map +1 -0
  81. package/dist/tests/tracer.test.js +38 -0
  82. package/dist/tests/tracer.test.js.map +1 -0
  83. package/dist/utils/hashing.d.ts +5 -0
  84. package/dist/utils/hashing.d.ts.map +1 -0
  85. package/dist/utils/hashing.js +18 -0
  86. package/dist/utils/hashing.js.map +1 -0
  87. package/dist/utils/time.d.ts +6 -0
  88. package/dist/utils/time.d.ts.map +1 -0
  89. package/dist/utils/time.js +22 -0
  90. package/dist/utils/time.js.map +1 -0
  91. package/package.json +63 -0
  92. package/sql/schema.sql +73 -0
@@ -0,0 +1,41 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { computeHallucinationRiskScore, riskLevelForScore, computeGroundingScore, computeNumericVarianceScore, detectToolClaimMismatch, } from '../hallucination/scoring.js';
4
+ test('Hallucination risk score computation', async () => {
5
+ const score = computeHallucinationRiskScore({
6
+ outputInstability: 0.5,
7
+ groundingScore: 0.8,
8
+ numericVarianceScore: 0.2,
9
+ selfConsistencyScore: 0.9,
10
+ toolClaimMismatch: false,
11
+ });
12
+ assert.ok(score >= 0 && score <= 1);
13
+ });
14
+ test('Risk level categorization', async () => {
15
+ assert.equal(riskLevelForScore(0.2), 'low');
16
+ assert.equal(riskLevelForScore(0.5), 'medium');
17
+ assert.equal(riskLevelForScore(0.8), 'high');
18
+ });
19
+ test('Grounding score computation', async () => {
20
+ const output = 'the deployment plan includes staging';
21
+ const context = 'deployment staging production';
22
+ const score = computeGroundingScore(output, context);
23
+ assert.ok(score >= 0 && score <= 1);
24
+ });
25
+ test('Numeric variance score computation', async () => {
26
+ const values = [10, 11, 12, 10, 11];
27
+ const score = computeNumericVarianceScore(values);
28
+ assert.ok(score >= 0 && score <= 1);
29
+ });
30
+ test('Numeric variance score handles low variance', async () => {
31
+ const values = [100, 100, 100];
32
+ const score = computeNumericVarianceScore(values);
33
+ assert.equal(score, 0);
34
+ });
35
+ test('Tool claim mismatch detection', async () => {
36
+ const mismatch1 = detectToolClaimMismatch('transfer_funds', 'deployment plan');
37
+ const mismatch2 = detectToolClaimMismatch('transfer_funds', 'transfer 1000 to account');
38
+ assert.equal(mismatch1, true);
39
+ assert.equal(mismatch2, false);
40
+ });
41
+ //# sourceMappingURL=scoring.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.test.js","sourceRoot":"","sources":["../../src/tests/scoring.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AAErC,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACtD,MAAM,KAAK,GAAG,6BAA6B,CAAC;QAC1C,iBAAiB,EAAE,GAAG;QACtB,cAAc,EAAE,GAAG;QACnB,oBAAoB,EAAE,GAAG;QACzB,oBAAoB,EAAE,GAAG;QACzB,iBAAiB,EAAE,KAAK;KACzB,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;IAC3C,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;IAC7C,MAAM,MAAM,GAAG,sCAAsC,CAAC;IACtD,MAAM,OAAO,GAAG,+BAA+B,CAAC;IAEhD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;IACpD,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;IAC7D,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACzB,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;IAC/C,MAAM,SAAS,GAAG,uBAAuB,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAC;IAC/E,MAAM,SAAS,GAAG,uBAAuB,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAAC;IAExF,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.test.d.ts","sourceRoot":"","sources":["../../src/tests/token.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { TokenManager } from '../proposal/token.js';
4
+ test('TokenManager issues and verifies tokens', async () => {
5
+ const manager = new TokenManager('test-secret');
6
+ const { token, payload } = manager.issueToken({
7
+ proposalId: 'proposal-123',
8
+ toolName: 'transfer_funds',
9
+ toolArgsHash: 'hash-abc',
10
+ compositeScore: 0.5,
11
+ });
12
+ assert.ok(token);
13
+ assert.equal(payload.proposalId, 'proposal-123');
14
+ assert.equal(payload.toolName, 'transfer_funds');
15
+ const result = manager.verifyToken(token);
16
+ assert.equal(result.valid, true);
17
+ assert.equal(result.payload?.proposalId, 'proposal-123');
18
+ });
19
+ test('TokenManager rejects invalid tokens', async () => {
20
+ const manager = new TokenManager('test-secret');
21
+ const result = manager.verifyToken('invalid-token');
22
+ assert.equal(result.valid, false);
23
+ });
24
+ test('TokenManager rejects expired tokens', async () => {
25
+ const manager = new TokenManager('test-secret');
26
+ const { token } = manager.issueToken({
27
+ proposalId: 'proposal-123',
28
+ toolName: 'test',
29
+ toolArgsHash: 'hash',
30
+ compositeScore: 0.5,
31
+ expiryMs: 1, // 1ms - will be expired immediately
32
+ });
33
+ await new Promise((resolve) => setTimeout(resolve, 10));
34
+ const result = manager.verifyToken(token);
35
+ assert.equal(result.valid, false);
36
+ assert.equal(result.reason, 'expired');
37
+ });
38
+ test('Token signature verification fails with wrong secret', async () => {
39
+ const manager1 = new TokenManager('secret-1');
40
+ const manager2 = new TokenManager('secret-2');
41
+ const { token } = manager1.issueToken({
42
+ proposalId: 'proposal-123',
43
+ toolName: 'test',
44
+ toolArgsHash: 'hash',
45
+ compositeScore: 0.5,
46
+ });
47
+ const result = manager2.verifyToken(token);
48
+ assert.equal(result.valid, false);
49
+ assert.equal(result.reason, 'bad_signature');
50
+ });
51
+ //# sourceMappingURL=token.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.test.js","sourceRoot":"","sources":["../../src/tests/token.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tracer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracer.test.d.ts","sourceRoot":"","sources":["../../src/tests/tracer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { Tracer } from '../core/tracer.js';
4
+ test('Tracer creates spans with unique IDs', async () => {
5
+ const tracer = new Tracer('test-service');
6
+ const span1 = tracer.startSpan({ model: 'gpt-4' });
7
+ const span2 = tracer.startSpan({ model: 'gpt-4' });
8
+ assert.notEqual(span1.spanId, span2.spanId);
9
+ assert.equal(span1.service, 'test-service');
10
+ assert.equal(span1.model, 'gpt-4');
11
+ });
12
+ test('Tracer span inheritance', async () => {
13
+ const tracer = new Tracer('test-service');
14
+ const parent = tracer.startSpan();
15
+ const child = tracer.startSpan({ parent });
16
+ assert.equal(child.traceId, parent.traceId);
17
+ assert.equal(child.parentSpanId, parent.spanId);
18
+ });
19
+ test('Span can be finished', async () => {
20
+ const tracer = new Tracer('test-service');
21
+ const span = tracer.startSpan();
22
+ assert.equal(span.endTime, undefined);
23
+ tracer.endSpan(span);
24
+ assert.notEqual(span.endTime, undefined);
25
+ });
26
+ test('withSpan context manager works', async () => {
27
+ const tracer = new Tracer('test-service');
28
+ let spanClosed = false;
29
+ await tracer.withSpan(async (span) => {
30
+ assert.equal(span.endTime, undefined);
31
+ return new Promise((resolve) => {
32
+ setTimeout(() => {
33
+ resolve(true);
34
+ }, 10);
35
+ });
36
+ });
37
+ });
38
+ //# sourceMappingURL=tracer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracer.test.js","sourceRoot":"","sources":["../../src/tests/tracer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAEnD,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;IACzC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE3C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;IACtC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEtC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;IAChD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACnC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,UAAU,CAAC,GAAG,EAAE;gBACd,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,EAAE,EAAE,CAAC,CAAC;QACT,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function hashText(text: string): string;
2
+ export declare function hashJson(obj: unknown): string;
3
+ export declare function normalizePrompt(prompt: string): string;
4
+ export declare function computePromptHash(prompt: string): string;
5
+ //# sourceMappingURL=hashing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing.d.ts","sourceRoot":"","sources":["../../src/utils/hashing.ts"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAG7C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD"}
@@ -0,0 +1,18 @@
1
+ import { createHash } from 'crypto';
2
+ export function hashText(text) {
3
+ return createHash('sha256').update(text).digest('hex');
4
+ }
5
+ export function hashJson(obj) {
6
+ const canonical = JSON.stringify(obj, Object.keys(obj).sort());
7
+ return hashText(canonical);
8
+ }
9
+ export function normalizePrompt(prompt) {
10
+ return prompt
11
+ .toLowerCase()
12
+ .trim()
13
+ .replace(/\s+/g, ' ');
14
+ }
15
+ export function computePromptHash(prompt) {
16
+ return hashText(normalizePrompt(prompt));
17
+ }
18
+ //# sourceMappingURL=hashing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashing.js","sourceRoot":"","sources":["../../src/utils/hashing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAY;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM;SACV,WAAW,EAAE;SACb,IAAI,EAAE;SACN,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function getCurrentTimeMs(): number;
2
+ export declare function getCurrentTimeIso(): string;
3
+ export declare function addMs(date: Date, ms: number): Date;
4
+ export declare function diffMs(start: Date, end: Date): number;
5
+ export declare function formatDuration(ms: number): string;
6
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMjD"}
@@ -0,0 +1,22 @@
1
+ export function getCurrentTimeMs() {
2
+ return Date.now();
3
+ }
4
+ export function getCurrentTimeIso() {
5
+ return new Date().toISOString();
6
+ }
7
+ export function addMs(date, ms) {
8
+ return new Date(date.getTime() + ms);
9
+ }
10
+ export function diffMs(start, end) {
11
+ return end.getTime() - start.getTime();
12
+ }
13
+ export function formatDuration(ms) {
14
+ if (ms < 1000)
15
+ return `${Math.round(ms)}ms`;
16
+ const seconds = ms / 1000;
17
+ if (seconds < 60)
18
+ return `${seconds.toFixed(2)}s`;
19
+ const minutes = seconds / 60;
20
+ return `${minutes.toFixed(2)}m`;
21
+ }
22
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAU,EAAE,EAAU;IAC1C,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAW,EAAE,GAAS;IAC3C,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;IAC5C,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC;IAC1B,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,GAAG,EAAE,CAAC;IAC7B,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAClC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@weaveaijs/mcp-observatory",
3
+ "version": "0.3.0",
4
+ "description": "Observability and APM toolkit for MCP servers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "sql"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18.0.0"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/rajatarun/mcp-observatory.git",
18
+ "directory": ".",
19
+ "branch": "main-nodejs"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/rajatarun/mcp-observatory/issues"
23
+ },
24
+ "homepage": "https://github.com/rajatarun/mcp-observatory#readme",
25
+ "keywords": [
26
+ "mcp",
27
+ "observability",
28
+ "apm",
29
+ "tracer",
30
+ "span",
31
+ "proposal-commit",
32
+ "risk-scoring",
33
+ "hallucination-detection"
34
+ ],
35
+ "author": "WeaveAI",
36
+ "license": "Apache-2.0",
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch",
44
+ "test": "node scripts/test.mjs",
45
+ "test:watch": "tsc --watch",
46
+ "lint": "eslint src/**/*.ts",
47
+ "demo": "node dist/demo/runDemo.js",
48
+ "demo:server": "node dist/demo/server.js",
49
+ "demo:client": "node dist/demo/client.js"
50
+ },
51
+ "dependencies": {
52
+ "pg": "^8.11.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^20.0.0",
56
+ "@types/pg": "^8.11.0",
57
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
58
+ "@typescript-eslint/parser": "^6.0.0",
59
+ "eslint": "^8.0.0",
60
+ "glob": "^10.5.0",
61
+ "typescript": "^5.0.0"
62
+ }
63
+ }
package/sql/schema.sql ADDED
@@ -0,0 +1,73 @@
1
+ -- MCP Observatory Schema for PostgreSQL
2
+
3
+ -- Proposals table: stores proposal records with status and expiration
4
+ CREATE TABLE IF NOT EXISTS proposals (
5
+ id UUID PRIMARY KEY,
6
+ tool_name VARCHAR(255) NOT NULL,
7
+ tool_args_hash VARCHAR(64) NOT NULL,
8
+ status VARCHAR(20) NOT NULL CHECK (status IN ('allowed', 'blocked', 'review')),
9
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
10
+ expires_at TIMESTAMP NOT NULL,
11
+ INDEX idx_proposals_expires_at (expires_at)
12
+ );
13
+
14
+ -- Commits table: tracks execution of proposals
15
+ CREATE TABLE IF NOT EXISTS commits (
16
+ id UUID PRIMARY KEY,
17
+ proposal_id UUID NOT NULL REFERENCES proposals(id),
18
+ executed BOOLEAN NOT NULL DEFAULT FALSE,
19
+ executed_at TIMESTAMP,
20
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
21
+ INDEX idx_commits_proposal_id (proposal_id),
22
+ INDEX idx_commits_executed (executed)
23
+ );
24
+
25
+ -- Nonces table: tracks used nonces to prevent replay attacks
26
+ CREATE TABLE IF NOT EXISTS nonces (
27
+ nonce VARCHAR(64) PRIMARY KEY,
28
+ used_at TIMESTAMP NOT NULL DEFAULT NOW(),
29
+ INDEX idx_nonces_used_at (used_at)
30
+ );
31
+
32
+ -- Tool prompt baselines table: stores baseline prompts for drift detection
33
+ CREATE TABLE IF NOT EXISTS tool_prompt_baselines (
34
+ id UUID PRIMARY KEY,
35
+ tool_name VARCHAR(255) NOT NULL UNIQUE,
36
+ baseline_prompt_hash VARCHAR(64) NOT NULL,
37
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
38
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
39
+ INDEX idx_baselines_tool_name (tool_name)
40
+ );
41
+
42
+ -- Traces table: stores execution traces for observability
43
+ CREATE TABLE IF NOT EXISTS traces (
44
+ trace_id UUID PRIMARY KEY,
45
+ span_id UUID NOT NULL,
46
+ parent_span_id UUID,
47
+ service VARCHAR(255) NOT NULL,
48
+ model VARCHAR(255),
49
+ tool_name VARCHAR(255),
50
+ start_time TIMESTAMP NOT NULL,
51
+ end_time TIMESTAMP,
52
+ input_hash VARCHAR(64),
53
+ output_hash VARCHAR(64),
54
+ input_tokens INTEGER,
55
+ output_tokens INTEGER,
56
+ total_tokens INTEGER,
57
+ cost_usd DECIMAL(10, 6),
58
+ status_code INTEGER,
59
+ tags JSONB,
60
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
61
+ INDEX idx_traces_trace_id (trace_id),
62
+ INDEX idx_traces_service (service),
63
+ INDEX idx_traces_created_at (created_at)
64
+ );
65
+
66
+ -- Clean up expired proposals periodically
67
+ CREATE OR REPLACE FUNCTION cleanup_expired_proposals()
68
+ RETURNS void AS $$
69
+ BEGIN
70
+ DELETE FROM proposals WHERE expires_at < NOW();
71
+ DELETE FROM nonces WHERE used_at < NOW() - INTERVAL '1 hour';
72
+ END;
73
+ $$ LANGUAGE plpgsql;