@jshookmcp/jshook 0.2.5 → 0.2.6

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 (210) hide show
  1. package/README.md +5 -5
  2. package/README.zh.md +5 -5
  3. package/dist/packages/extension-sdk/src/workflow.d.ts +17 -2
  4. package/dist/packages/extension-sdk/src/workflow.js +36 -0
  5. package/dist/src/modules/browser/BrowserPool.d.ts +49 -0
  6. package/dist/src/modules/browser/BrowserPool.js +288 -0
  7. package/dist/src/modules/deobfuscator/AdvancedDeobfuscator.d.ts +5 -0
  8. package/dist/src/modules/deobfuscator/AdvancedDeobfuscator.js +43 -2
  9. package/dist/src/modules/deobfuscator/Deobfuscator.js +5 -0
  10. package/dist/src/modules/external/ExternalToolRunner.js +1 -1
  11. package/dist/src/server/MCPServer.context.d.ts +1 -0
  12. package/dist/src/server/domains/browser/handlers/stealth-injection.d.ts +1 -0
  13. package/dist/src/server/domains/browser/handlers/stealth-injection.js +3 -0
  14. package/dist/src/server/domains/shared-state-board/definitions.d.ts +2 -0
  15. package/dist/src/server/domains/shared-state-board/definitions.js +78 -0
  16. package/dist/src/server/domains/shared-state-board/handlers.impl.d.ts +58 -0
  17. package/dist/src/server/domains/shared-state-board/handlers.impl.js +419 -0
  18. package/dist/src/server/domains/shared-state-board/index.d.ts +2 -0
  19. package/dist/src/server/domains/shared-state-board/index.js +2 -0
  20. package/dist/src/server/domains/shared-state-board/manifest.d.ts +57 -0
  21. package/dist/src/server/domains/shared-state-board/manifest.js +74 -0
  22. package/dist/src/server/http/SseStream.d.ts +21 -0
  23. package/dist/src/server/http/SseStream.js +129 -0
  24. package/dist/src/server/teams/TeamManager.d.ts +43 -0
  25. package/dist/src/server/teams/TeamManager.js +238 -0
  26. package/dist/src/server/teams/index.d.ts +1 -0
  27. package/dist/src/server/teams/index.js +1 -0
  28. package/dist/src/server/workflows/WorkflowContract.d.ts +20 -4
  29. package/dist/src/server/workflows/WorkflowContract.js +40 -0
  30. package/dist/src/server/workflows/WorkflowEngine.js +190 -13
  31. package/dist/src/types/deobfuscator.d.ts +1 -0
  32. package/dist/src/utils/cache/CachedDecorator.d.ts +8 -0
  33. package/dist/src/utils/cache/CachedDecorator.js +55 -0
  34. package/dist/src/utils/cache/PersistentCache.d.ts +33 -0
  35. package/dist/src/utils/cache/PersistentCache.js +246 -0
  36. package/dist/src/utils/cache/index.d.ts +2 -0
  37. package/dist/src/utils/cache/index.js +2 -0
  38. package/package.json +11 -12
  39. package/scripts/postinstall.cjs +54 -27
  40. package/workflows/anti-bot-diagnoser/.jshook-install.json +14 -0
  41. package/workflows/anti-bot-diagnoser/LICENSE +21 -0
  42. package/workflows/anti-bot-diagnoser/README.md +105 -0
  43. package/workflows/anti-bot-diagnoser/docs/agent-recipes.md +44 -0
  44. package/workflows/anti-bot-diagnoser/meta.yaml +6 -0
  45. package/workflows/anti-bot-diagnoser/package.json +22 -0
  46. package/workflows/anti-bot-diagnoser/tsconfig.json +15 -0
  47. package/workflows/anti-bot-diagnoser/workflow.ts +224 -0
  48. package/workflows/api-openapi-probe/.jshook-install.json +14 -0
  49. package/workflows/api-openapi-probe/meta.yaml +6 -0
  50. package/workflows/api-openapi-probe/package.json +22 -0
  51. package/workflows/api-openapi-probe/pnpm-lock.yaml +819 -0
  52. package/workflows/api-openapi-probe/tsconfig.json +15 -0
  53. package/workflows/api-openapi-probe/workflow.ts +40 -0
  54. package/workflows/api-probe-batch/.jshook-install.json +14 -0
  55. package/workflows/api-probe-batch/LICENSE +21 -0
  56. package/workflows/api-probe-batch/README.md +45 -0
  57. package/workflows/api-probe-batch/meta.yaml +4 -0
  58. package/workflows/api-probe-batch/package.json +23 -0
  59. package/workflows/api-probe-batch/tsconfig.json +16 -0
  60. package/workflows/api-probe-batch/workflow.ts +111 -0
  61. package/workflows/auth-bootstrap/.jshook-install.json +14 -0
  62. package/workflows/auth-bootstrap/LICENSE +21 -0
  63. package/workflows/auth-bootstrap/README.md +74 -0
  64. package/workflows/auth-bootstrap/meta.yaml +4 -0
  65. package/workflows/auth-bootstrap/package.json +23 -0
  66. package/workflows/auth-bootstrap/tsconfig.json +16 -0
  67. package/workflows/auth-bootstrap/workflow.ts +141 -0
  68. package/workflows/auth-extract/.jshook-install.json +14 -0
  69. package/workflows/auth-extract/meta.yaml +6 -0
  70. package/workflows/auth-extract/package.json +22 -0
  71. package/workflows/auth-extract/pnpm-lock.yaml +819 -0
  72. package/workflows/auth-extract/tsconfig.json +15 -0
  73. package/workflows/auth-extract/workflow.ts +36 -0
  74. package/workflows/auth-surface-mapper/.jshook-install.json +14 -0
  75. package/workflows/auth-surface-mapper/meta.yaml +6 -0
  76. package/workflows/auth-surface-mapper/package.json +22 -0
  77. package/workflows/auth-surface-mapper/pnpm-lock.yaml +819 -0
  78. package/workflows/auth-surface-mapper/tsconfig.json +15 -0
  79. package/workflows/auth-surface-mapper/workflow.ts +104 -0
  80. package/workflows/batch-register/.jshook-install.json +14 -0
  81. package/workflows/batch-register/LICENSE +21 -0
  82. package/workflows/batch-register/README.md +39 -0
  83. package/workflows/batch-register/meta.yaml +4 -0
  84. package/workflows/batch-register/package.json +23 -0
  85. package/workflows/batch-register/tsconfig.json +16 -0
  86. package/workflows/batch-register/workflow.ts +67 -0
  87. package/workflows/bundle-recovery/.jshook-install.json +14 -0
  88. package/workflows/bundle-recovery/LICENSE +21 -0
  89. package/workflows/bundle-recovery/README.md +105 -0
  90. package/workflows/bundle-recovery/docs/agent-recipes.md +44 -0
  91. package/workflows/bundle-recovery/meta.yaml +6 -0
  92. package/workflows/bundle-recovery/package.json +22 -0
  93. package/workflows/bundle-recovery/tsconfig.json +15 -0
  94. package/workflows/bundle-recovery/workflow.ts +179 -0
  95. package/workflows/challenge-detector/.jshook-install.json +14 -0
  96. package/workflows/challenge-detector/meta.yaml +14 -0
  97. package/workflows/challenge-detector/package.json +22 -0
  98. package/workflows/challenge-detector/pnpm-lock.yaml +819 -0
  99. package/workflows/challenge-detector/tsconfig.json +15 -0
  100. package/workflows/challenge-detector/workflow.ts +298 -0
  101. package/workflows/deobfuscation-pipeline/.jshook-install.json +14 -0
  102. package/workflows/deobfuscation-pipeline/meta.yaml +6 -0
  103. package/workflows/deobfuscation-pipeline/package.json +22 -0
  104. package/workflows/deobfuscation-pipeline/pnpm-lock.yaml +819 -0
  105. package/workflows/deobfuscation-pipeline/tsconfig.json +15 -0
  106. package/workflows/deobfuscation-pipeline/workflow.ts +119 -0
  107. package/workflows/electron-bridge-mapper/.jshook-install.json +14 -0
  108. package/workflows/electron-bridge-mapper/meta.yaml +6 -0
  109. package/workflows/electron-bridge-mapper/package.json +22 -0
  110. package/workflows/electron-bridge-mapper/pnpm-lock.yaml +819 -0
  111. package/workflows/electron-bridge-mapper/tsconfig.json +15 -0
  112. package/workflows/electron-bridge-mapper/workflow.ts +125 -0
  113. package/workflows/evidence-pack/.jshook-install.json +14 -0
  114. package/workflows/evidence-pack/LICENSE +21 -0
  115. package/workflows/evidence-pack/README.md +105 -0
  116. package/workflows/evidence-pack/docs/agent-recipes.md +44 -0
  117. package/workflows/evidence-pack/meta.yaml +6 -0
  118. package/workflows/evidence-pack/package.json +22 -0
  119. package/workflows/evidence-pack/tsconfig.json +15 -0
  120. package/workflows/evidence-pack/workflow.ts +154 -0
  121. package/workflows/js-bundle-search/.jshook-install.json +14 -0
  122. package/workflows/js-bundle-search/LICENSE +21 -0
  123. package/workflows/js-bundle-search/README.md +46 -0
  124. package/workflows/js-bundle-search/meta.yaml +4 -0
  125. package/workflows/js-bundle-search/package.json +23 -0
  126. package/workflows/js-bundle-search/tsconfig.json +16 -0
  127. package/workflows/js-bundle-search/workflow.ts +118 -0
  128. package/workflows/protocol-registry/.jshook-install.json +14 -0
  129. package/workflows/protocol-registry/meta.yaml +6 -0
  130. package/workflows/protocol-registry/package.json +22 -0
  131. package/workflows/protocol-registry/pnpm-lock.yaml +819 -0
  132. package/workflows/protocol-registry/tsconfig.json +15 -0
  133. package/workflows/protocol-registry/workflow.ts +107 -0
  134. package/workflows/qwen-mail-open-latest/meta.yaml +7 -0
  135. package/workflows/qwen-mail-open-latest/package.json +22 -0
  136. package/workflows/qwen-mail-open-latest/pnpm-lock.yaml +819 -0
  137. package/workflows/qwen-mail-open-latest/tsconfig.json +15 -0
  138. package/workflows/qwen-mail-open-latest/workflow.ts +77 -0
  139. package/workflows/register-account-flow/.jshook-install.json +14 -0
  140. package/workflows/register-account-flow/LICENSE +21 -0
  141. package/workflows/register-account-flow/README.md +64 -0
  142. package/workflows/register-account-flow/meta.yaml +4 -0
  143. package/workflows/register-account-flow/package.json +23 -0
  144. package/workflows/register-account-flow/tsconfig.json +16 -0
  145. package/workflows/register-account-flow/workflow.ts +127 -0
  146. package/workflows/replay-lab/.jshook-install.json +14 -0
  147. package/workflows/replay-lab/meta.yaml +6 -0
  148. package/workflows/replay-lab/package.json +22 -0
  149. package/workflows/replay-lab/pnpm-lock.yaml +819 -0
  150. package/workflows/replay-lab/tsconfig.json +15 -0
  151. package/workflows/replay-lab/workflow.ts +106 -0
  152. package/workflows/script-evidence-scan/.jshook-install.json +14 -0
  153. package/workflows/script-evidence-scan/LICENSE +21 -0
  154. package/workflows/script-evidence-scan/README.md +61 -0
  155. package/workflows/script-evidence-scan/meta.yaml +4 -0
  156. package/workflows/script-evidence-scan/package.json +23 -0
  157. package/workflows/script-evidence-scan/tsconfig.json +16 -0
  158. package/workflows/script-evidence-scan/workflow.ts +89 -0
  159. package/workflows/signature-hunter/.jshook-install.json +14 -0
  160. package/workflows/signature-hunter/LICENSE +21 -0
  161. package/workflows/signature-hunter/README.md +105 -0
  162. package/workflows/signature-hunter/docs/agent-recipes.md +44 -0
  163. package/workflows/signature-hunter/meta.yaml +6 -0
  164. package/workflows/signature-hunter/package.json +22 -0
  165. package/workflows/signature-hunter/tsconfig.json +15 -0
  166. package/workflows/signature-hunter/workflow.ts +170 -0
  167. package/workflows/signing-lineage/.jshook-install.json +14 -0
  168. package/workflows/signing-lineage/meta.yaml +6 -0
  169. package/workflows/signing-lineage/package.json +22 -0
  170. package/workflows/signing-lineage/pnpm-lock.yaml +819 -0
  171. package/workflows/signing-lineage/tsconfig.json +15 -0
  172. package/workflows/signing-lineage/workflow.ts +120 -0
  173. package/workflows/temp-mail-extract-link/.jshook-install.json +14 -0
  174. package/workflows/temp-mail-extract-link/LICENSE +21 -0
  175. package/workflows/temp-mail-extract-link/README.md +71 -0
  176. package/workflows/temp-mail-extract-link/meta.yaml +4 -0
  177. package/workflows/temp-mail-extract-link/package.json +23 -0
  178. package/workflows/temp-mail-extract-link/tsconfig.json +16 -0
  179. package/workflows/temp-mail-extract-link/workflow.ts +221 -0
  180. package/workflows/temp-mail-open-latest/.jshook-install.json +14 -0
  181. package/workflows/temp-mail-open-latest/LICENSE +21 -0
  182. package/workflows/temp-mail-open-latest/README.md +61 -0
  183. package/workflows/temp-mail-open-latest/meta.yaml +4 -0
  184. package/workflows/temp-mail-open-latest/package.json +23 -0
  185. package/workflows/temp-mail-open-latest/tsconfig.json +16 -0
  186. package/workflows/temp-mail-open-latest/workflow.ts +136 -0
  187. package/workflows/template/.jshook-install.json +14 -0
  188. package/workflows/template/LICENSE +21 -0
  189. package/workflows/template/README.md +45 -0
  190. package/workflows/template/docs/SKILL.md +111 -0
  191. package/workflows/template/meta.yaml +6 -0
  192. package/workflows/template/package.json +22 -0
  193. package/workflows/template/pnpm-lock.yaml +819 -0
  194. package/workflows/template/tsconfig.json +15 -0
  195. package/workflows/template/workflow.ts +73 -0
  196. package/workflows/web-api-capture-session/.jshook-install.json +14 -0
  197. package/workflows/web-api-capture-session/LICENSE +21 -0
  198. package/workflows/web-api-capture-session/README.md +64 -0
  199. package/workflows/web-api-capture-session/meta.yaml +4 -0
  200. package/workflows/web-api-capture-session/package.json +23 -0
  201. package/workflows/web-api-capture-session/tsconfig.json +16 -0
  202. package/workflows/web-api-capture-session/workflow.ts +124 -0
  203. package/workflows/ws-protocol-lifter/.jshook-install.json +14 -0
  204. package/workflows/ws-protocol-lifter/LICENSE +21 -0
  205. package/workflows/ws-protocol-lifter/README.md +105 -0
  206. package/workflows/ws-protocol-lifter/docs/agent-recipes.md +44 -0
  207. package/workflows/ws-protocol-lifter/meta.yaml +6 -0
  208. package/workflows/ws-protocol-lifter/package.json +22 -0
  209. package/workflows/ws-protocol-lifter/tsconfig.json +15 -0
  210. package/workflows/ws-protocol-lifter/workflow.ts +163 -0
@@ -0,0 +1,78 @@
1
+ import { tool } from '../../registry/tool-builder.js';
2
+ export const sharedStateBoardTools = [
3
+ tool('state_board_set')
4
+ .desc('Set a value in the shared state board. Supports string, number, boolean, object, and array values.')
5
+ .string('key', 'The key to store the value under')
6
+ .prop('value', {
7
+ type: 'object',
8
+ description: 'The value to store (any JSON-serializable type)',
9
+ })
10
+ .string('namespace', 'Optional namespace for key isolation (default: "default")')
11
+ .number('ttlSeconds', 'Optional TTL in seconds (value expires after this duration)')
12
+ .required('key', 'value')
13
+ .build(),
14
+ tool('state_board_get')
15
+ .desc('Get a value from the shared state board by key.')
16
+ .string('key', 'The key to retrieve')
17
+ .string('namespace', 'Optional namespace (default: "default")')
18
+ .readOnly()
19
+ .idempotent()
20
+ .required('key')
21
+ .build(),
22
+ tool('state_board_delete')
23
+ .desc('Delete a value from the shared state board by key.')
24
+ .string('key', 'The key to delete')
25
+ .string('namespace', 'Optional namespace (default: "default")')
26
+ .required('key')
27
+ .build(),
28
+ tool('state_board_list')
29
+ .desc('List all keys in the shared state board, optionally filtered by namespace.')
30
+ .string('namespace', 'Optional namespace filter (default: all namespaces)')
31
+ .boolean('includeValues', 'Include current values in the response (default: false)')
32
+ .readOnly()
33
+ .idempotent()
34
+ .build(),
35
+ tool('state_board_watch')
36
+ .desc('Watch a key or pattern for changes. Returns a watch ID that can be used to poll for updates.')
37
+ .string('key', 'The key to watch (supports * wildcard for pattern matching)')
38
+ .string('namespace', 'Optional namespace (default: "default")')
39
+ .number('pollIntervalMs', 'Polling interval in milliseconds (default: 1000)')
40
+ .required('key')
41
+ .build(),
42
+ tool('state_board_unwatch')
43
+ .desc('Stop watching a key or pattern.')
44
+ .string('watchId', 'The watch ID returned by state_board_watch')
45
+ .required('watchId')
46
+ .build(),
47
+ tool('state_board_history')
48
+ .desc('Get the change history for a key.')
49
+ .string('key', 'The key to get history for')
50
+ .string('namespace', 'Optional namespace (default: "default")')
51
+ .number('limit', 'Maximum number of history entries to return (default: 50)')
52
+ .readOnly()
53
+ .idempotent()
54
+ .required('key')
55
+ .build(),
56
+ tool('state_board_export')
57
+ .desc('Export all or filtered state board entries as JSON.')
58
+ .string('namespace', 'Optional namespace filter (default: all)')
59
+ .string('keyPattern', 'Optional key pattern filter (supports * wildcard)')
60
+ .readOnly()
61
+ .idempotent()
62
+ .build(),
63
+ tool('state_board_import')
64
+ .desc('Import state board entries from JSON. Merges with existing state.')
65
+ .prop('data', {
66
+ type: 'object',
67
+ description: 'Object with keys and values to import',
68
+ })
69
+ .string('namespace', 'Optional namespace (default: "default")')
70
+ .boolean('overwrite', 'Overwrite existing keys (default: false)')
71
+ .required('data')
72
+ .build(),
73
+ tool('state_board_clear')
74
+ .desc('Clear all or filtered state board entries.')
75
+ .string('namespace', 'Optional namespace to clear (default: all)')
76
+ .string('keyPattern', 'Optional key pattern to clear (supports * wildcard)')
77
+ .build(),
78
+ ];
@@ -0,0 +1,58 @@
1
+ export * from './definitions.js';
2
+ export interface StateEntry {
3
+ key: string;
4
+ value: unknown;
5
+ namespace: string;
6
+ createdAt: number;
7
+ updatedAt: number;
8
+ ttlSeconds?: number;
9
+ expiresAt?: number;
10
+ version: number;
11
+ }
12
+ export interface StateChangeRecord {
13
+ id: string;
14
+ key: string;
15
+ namespace: string;
16
+ action: 'set' | 'delete' | 'expire';
17
+ oldValue?: unknown;
18
+ newValue?: unknown;
19
+ timestamp: number;
20
+ source?: string;
21
+ }
22
+ export interface StateWatch {
23
+ id: string;
24
+ key: string;
25
+ namespace: string;
26
+ pattern: boolean;
27
+ pollIntervalMs: number;
28
+ lastChecked: number;
29
+ lastVersion: Record<string, number>;
30
+ createdAt: number;
31
+ }
32
+ export interface StateBoardStats {
33
+ totalEntries: number;
34
+ entriesByNamespace: Record<string, number>;
35
+ expiredEntries: number;
36
+ totalWatches: number;
37
+ historySize: number;
38
+ }
39
+ export declare class SharedStateBoardHandlers {
40
+ private readonly state;
41
+ private readonly history;
42
+ private readonly watches;
43
+ private readonly maxHistoryPerKey;
44
+ handleSet(args: Record<string, unknown>): Promise<unknown>;
45
+ handleGet(args: Record<string, unknown>): Promise<unknown>;
46
+ handleDelete(args: Record<string, unknown>): Promise<unknown>;
47
+ handleList(args: Record<string, unknown>): Promise<unknown>;
48
+ handleWatch(args: Record<string, unknown>): Promise<unknown>;
49
+ handleUnwatch(args: Record<string, unknown>): Promise<unknown>;
50
+ handlePoll(args: Record<string, unknown>): Promise<unknown>;
51
+ handleHistory(args: Record<string, unknown>): Promise<unknown>;
52
+ handleExport(args: Record<string, unknown>): Promise<unknown>;
53
+ handleImport(args: Record<string, unknown>): Promise<unknown>;
54
+ handleClear(args: Record<string, unknown>): Promise<unknown>;
55
+ handleStats(): Promise<unknown>;
56
+ private recordChange;
57
+ cleanupExpired(): number;
58
+ }
@@ -0,0 +1,419 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export * from './definitions.js';
3
+ export class SharedStateBoardHandlers {
4
+ state = new Map();
5
+ history = new Map();
6
+ watches = new Map();
7
+ maxHistoryPerKey = 100;
8
+ async handleSet(args) {
9
+ const key = args.key;
10
+ const value = args.value;
11
+ const namespace = args.namespace ?? 'default';
12
+ const ttlSeconds = args.ttlSeconds;
13
+ if (!key || typeof key !== 'string') {
14
+ throw new Error('key must be a non-empty string');
15
+ }
16
+ const fullKey = `${namespace}:${key}`;
17
+ const now = Date.now();
18
+ const existing = this.state.get(fullKey);
19
+ const oldVersion = existing?.version ?? 0;
20
+ const oldValue = existing?.value;
21
+ const entry = {
22
+ key,
23
+ value,
24
+ namespace,
25
+ createdAt: existing?.createdAt ?? now,
26
+ updatedAt: now,
27
+ ttlSeconds,
28
+ expiresAt: ttlSeconds ? now + ttlSeconds * 1000 : undefined,
29
+ version: oldVersion + 1,
30
+ };
31
+ this.state.set(fullKey, entry);
32
+ this.recordChange(fullKey, {
33
+ id: randomUUID().slice(0, 8),
34
+ key,
35
+ namespace,
36
+ action: 'set',
37
+ oldValue,
38
+ newValue: value,
39
+ timestamp: now,
40
+ });
41
+ return {
42
+ success: true,
43
+ key,
44
+ namespace,
45
+ version: entry.version,
46
+ expiresAt: entry.expiresAt ? new Date(entry.expiresAt).toISOString() : undefined,
47
+ };
48
+ }
49
+ async handleGet(args) {
50
+ const key = args.key;
51
+ const namespace = args.namespace ?? 'default';
52
+ if (!key || typeof key !== 'string') {
53
+ throw new Error('key must be a non-empty string');
54
+ }
55
+ const fullKey = `${namespace}:${key}`;
56
+ const entry = this.state.get(fullKey);
57
+ if (!entry) {
58
+ return { found: false, key, namespace };
59
+ }
60
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
61
+ this.handleDelete({ key, namespace });
62
+ return { found: false, key, namespace, expired: true };
63
+ }
64
+ return {
65
+ found: true,
66
+ key,
67
+ namespace,
68
+ value: entry.value,
69
+ version: entry.version,
70
+ createdAt: new Date(entry.createdAt).toISOString(),
71
+ updatedAt: new Date(entry.updatedAt).toISOString(),
72
+ ttlSeconds: entry.ttlSeconds,
73
+ expiresAt: entry.expiresAt ? new Date(entry.expiresAt).toISOString() : undefined,
74
+ };
75
+ }
76
+ async handleDelete(args) {
77
+ const key = args.key;
78
+ const namespace = args.namespace ?? 'default';
79
+ const fullKey = `${namespace}:${key}`;
80
+ const existing = this.state.get(fullKey);
81
+ if (!existing) {
82
+ return { deleted: false, key, namespace, reason: 'not_found' };
83
+ }
84
+ this.state.delete(fullKey);
85
+ this.recordChange(fullKey, {
86
+ id: randomUUID().slice(0, 8),
87
+ key,
88
+ namespace,
89
+ action: 'delete',
90
+ oldValue: existing.value,
91
+ timestamp: Date.now(),
92
+ });
93
+ return { deleted: true, key, namespace };
94
+ }
95
+ async handleList(args) {
96
+ const namespace = args.namespace;
97
+ const includeValues = args.includeValues ?? false;
98
+ const now = Date.now();
99
+ const entries = [];
100
+ for (const entry of this.state.values()) {
101
+ if (namespace && entry.namespace !== namespace) {
102
+ continue;
103
+ }
104
+ const expired = !!(entry.expiresAt && now > entry.expiresAt);
105
+ if (expired) {
106
+ this.handleDelete({ key: entry.key, namespace: entry.namespace });
107
+ continue;
108
+ }
109
+ entries.push({
110
+ key: entry.key,
111
+ namespace: entry.namespace,
112
+ version: entry.version,
113
+ updatedAt: new Date(entry.updatedAt).toISOString(),
114
+ ...(includeValues ? { value: entry.value } : {}),
115
+ });
116
+ }
117
+ entries.sort((a, b) => {
118
+ if (a.namespace !== b.namespace) {
119
+ return a.namespace.localeCompare(b.namespace);
120
+ }
121
+ return a.key.localeCompare(b.key);
122
+ });
123
+ return {
124
+ entries,
125
+ total: entries.length,
126
+ namespaces: [...new Set(entries.map((e) => e.namespace))],
127
+ };
128
+ }
129
+ async handleWatch(args) {
130
+ const key = args.key;
131
+ const namespace = args.namespace ?? 'default';
132
+ const pollIntervalMs = args.pollIntervalMs ?? 1000;
133
+ if (!key || typeof key !== 'string') {
134
+ throw new Error('key must be a non-empty string');
135
+ }
136
+ const watchId = `watch_${randomUUID().slice(0, 8)}`;
137
+ const isPattern = key.includes('*');
138
+ const watch = {
139
+ id: watchId,
140
+ key,
141
+ namespace,
142
+ pattern: isPattern,
143
+ pollIntervalMs,
144
+ lastChecked: Date.now(),
145
+ lastVersion: {},
146
+ createdAt: Date.now(),
147
+ };
148
+ const prefix = `${namespace}:`;
149
+ for (const [fullKey, entry] of this.state.entries()) {
150
+ if (fullKey.startsWith(prefix)) {
151
+ if (isPattern) {
152
+ const regex = new RegExp(`^${prefix}${key.replace(/\*/g, '.*')}$`);
153
+ if (regex.test(fullKey)) {
154
+ watch.lastVersion[fullKey] = entry.version;
155
+ }
156
+ }
157
+ else {
158
+ watch.lastVersion[fullKey] = entry.version;
159
+ }
160
+ }
161
+ }
162
+ this.watches.set(watchId, watch);
163
+ return {
164
+ watchId,
165
+ key,
166
+ namespace,
167
+ pattern: isPattern,
168
+ pollIntervalMs,
169
+ initialKeys: Object.keys(watch.lastVersion),
170
+ };
171
+ }
172
+ async handleUnwatch(args) {
173
+ const watchId = args.watchId;
174
+ const watch = this.watches.get(watchId);
175
+ if (!watch) {
176
+ return { removed: false, watchId, reason: 'not_found' };
177
+ }
178
+ this.watches.delete(watchId);
179
+ return { removed: true, watchId, wasWatching: watch.key };
180
+ }
181
+ async handlePoll(args) {
182
+ const watchId = args.watchId;
183
+ const watch = this.watches.get(watchId);
184
+ if (!watch) {
185
+ throw new Error(`Watch "${watchId}" not found`);
186
+ }
187
+ const now = Date.now();
188
+ const changes = [];
189
+ const prefix = `${watch.namespace}:`;
190
+ if (watch.pattern) {
191
+ const regex = new RegExp(`^${prefix}${watch.key.replace(/\*/g, '.*')}$`);
192
+ for (const [fullKey, entry] of this.state.entries()) {
193
+ if (regex.test(fullKey)) {
194
+ const lastVer = watch.lastVersion[fullKey];
195
+ if (lastVer === undefined) {
196
+ changes.push({ key: entry.key, namespace: entry.namespace, action: 'created' });
197
+ }
198
+ else if (entry.version > lastVer) {
199
+ changes.push({ key: entry.key, namespace: entry.namespace, action: 'changed' });
200
+ }
201
+ watch.lastVersion[fullKey] = entry.version;
202
+ }
203
+ }
204
+ for (const watchedKey of Object.keys(watch.lastVersion)) {
205
+ if (!this.state.has(watchedKey) && regex.test(watchedKey)) {
206
+ const idx = watchedKey.indexOf(':');
207
+ const key = idx >= 0 ? watchedKey.substring(idx + 1) : watchedKey;
208
+ changes.push({
209
+ key,
210
+ namespace: watch.namespace,
211
+ action: 'deleted',
212
+ });
213
+ delete watch.lastVersion[watchedKey];
214
+ }
215
+ }
216
+ }
217
+ else {
218
+ const fullKey = `${watch.namespace}:${watch.key}`;
219
+ const entry = this.state.get(fullKey);
220
+ const lastVer = watch.lastVersion[fullKey];
221
+ if (!entry && lastVer !== undefined) {
222
+ changes.push({ key: watch.key, namespace: watch.namespace, action: 'deleted' });
223
+ delete watch.lastVersion[fullKey];
224
+ }
225
+ else if (entry) {
226
+ if (lastVer === undefined) {
227
+ changes.push({ key: entry.key, namespace: entry.namespace, action: 'created' });
228
+ }
229
+ else if (entry.version > lastVer) {
230
+ changes.push({ key: entry.key, namespace: entry.namespace, action: 'changed' });
231
+ }
232
+ watch.lastVersion[fullKey] = entry.version;
233
+ }
234
+ }
235
+ watch.lastChecked = now;
236
+ return {
237
+ watchId,
238
+ changes,
239
+ hasChanges: changes.length > 0,
240
+ checkedAt: new Date(now).toISOString(),
241
+ };
242
+ }
243
+ async handleHistory(args) {
244
+ const key = args.key;
245
+ const namespace = args.namespace ?? 'default';
246
+ const limit = args.limit ?? 50;
247
+ const fullKey = `${namespace}:${key}`;
248
+ const records = this.history.get(fullKey) ?? [];
249
+ const sorted = [...records].toSorted((a, b) => b.timestamp - a.timestamp);
250
+ const limited = sorted.slice(0, limit);
251
+ return {
252
+ key,
253
+ namespace,
254
+ history: limited.map((r) => ({
255
+ ...r,
256
+ timestamp: new Date(r.timestamp).toISOString(),
257
+ })),
258
+ total: records.length,
259
+ returned: limited.length,
260
+ };
261
+ }
262
+ async handleExport(args) {
263
+ const namespace = args.namespace;
264
+ const keyPattern = args.keyPattern;
265
+ const now = Date.now();
266
+ const data = {};
267
+ for (const [_fullKey, entry] of this.state.entries()) {
268
+ if (namespace && entry.namespace !== namespace) {
269
+ continue;
270
+ }
271
+ if (keyPattern) {
272
+ const regex = new RegExp(`^${keyPattern.replace(/\*/g, '.*')}$`);
273
+ if (!regex.test(entry.key)) {
274
+ continue;
275
+ }
276
+ }
277
+ if (entry.expiresAt && now > entry.expiresAt) {
278
+ continue;
279
+ }
280
+ data[entry.key] = entry.value;
281
+ }
282
+ return {
283
+ data,
284
+ count: Object.keys(data).length,
285
+ namespace: namespace ?? 'all',
286
+ exportedAt: new Date(now).toISOString(),
287
+ };
288
+ }
289
+ async handleImport(args) {
290
+ const data = args.data;
291
+ const namespace = args.namespace ?? 'default';
292
+ const overwrite = args.overwrite ?? false;
293
+ if (!data || typeof data !== 'object') {
294
+ throw new Error('data must be an object');
295
+ }
296
+ const imported = [];
297
+ const skipped = [];
298
+ const overwritten = [];
299
+ for (const [key, value] of Object.entries(data)) {
300
+ const fullKey = `${namespace}:${key}`;
301
+ const existing = this.state.get(fullKey);
302
+ if (existing && !overwrite) {
303
+ skipped.push(key);
304
+ continue;
305
+ }
306
+ if (existing && overwrite) {
307
+ overwritten.push(key);
308
+ }
309
+ const now = Date.now();
310
+ const entry = {
311
+ key,
312
+ value,
313
+ namespace,
314
+ createdAt: existing?.createdAt ?? now,
315
+ updatedAt: now,
316
+ version: (existing?.version ?? 0) + 1,
317
+ };
318
+ this.state.set(fullKey, entry);
319
+ imported.push(key);
320
+ }
321
+ return {
322
+ imported: imported.length,
323
+ skipped: skipped.length,
324
+ overwritten: overwritten.length,
325
+ total: Object.keys(data).length,
326
+ keys: imported,
327
+ };
328
+ }
329
+ async handleClear(args) {
330
+ const namespace = args.namespace;
331
+ const keyPattern = args.keyPattern;
332
+ const toDelete = [];
333
+ for (const [fullKey, entry] of this.state.entries()) {
334
+ if (namespace && entry.namespace !== namespace) {
335
+ continue;
336
+ }
337
+ if (keyPattern) {
338
+ const regex = new RegExp(`^${keyPattern.replace(/\*/g, '.*')}$`);
339
+ if (!regex.test(entry.key)) {
340
+ continue;
341
+ }
342
+ }
343
+ toDelete.push(fullKey);
344
+ }
345
+ for (const fullKey of toDelete) {
346
+ const entry = this.state.get(fullKey);
347
+ if (entry) {
348
+ this.state.delete(fullKey);
349
+ this.recordChange(fullKey, {
350
+ id: randomUUID().slice(0, 8),
351
+ key: entry.key,
352
+ namespace: entry.namespace,
353
+ action: 'delete',
354
+ oldValue: entry.value,
355
+ timestamp: Date.now(),
356
+ });
357
+ }
358
+ }
359
+ return {
360
+ cleared: toDelete.length,
361
+ namespace: namespace ?? 'all',
362
+ pattern: keyPattern,
363
+ };
364
+ }
365
+ async handleStats() {
366
+ const now = Date.now();
367
+ const entriesByNamespace = {};
368
+ let expiredCount = 0;
369
+ for (const [, entry] of this.state.entries()) {
370
+ if (entry.expiresAt && now > entry.expiresAt) {
371
+ expiredCount++;
372
+ continue;
373
+ }
374
+ entriesByNamespace[entry.namespace] = (entriesByNamespace[entry.namespace] ?? 0) + 1;
375
+ }
376
+ let historySize = 0;
377
+ for (const records of this.history.values()) {
378
+ historySize += records.length;
379
+ }
380
+ const stats = {
381
+ totalEntries: Object.values(entriesByNamespace).reduce((a, b) => a + b, 0),
382
+ entriesByNamespace,
383
+ expiredEntries: expiredCount,
384
+ totalWatches: this.watches.size,
385
+ historySize,
386
+ };
387
+ return stats;
388
+ }
389
+ recordChange(fullKey, record) {
390
+ let history = this.history.get(fullKey);
391
+ if (!history) {
392
+ history = [];
393
+ this.history.set(fullKey, history);
394
+ }
395
+ history.push(record);
396
+ if (history.length > this.maxHistoryPerKey) {
397
+ history.splice(0, history.length - this.maxHistoryPerKey);
398
+ }
399
+ }
400
+ cleanupExpired() {
401
+ const now = Date.now();
402
+ let cleaned = 0;
403
+ for (const [fullKey, entry] of this.state.entries()) {
404
+ if (entry.expiresAt && now > entry.expiresAt) {
405
+ this.state.delete(fullKey);
406
+ this.recordChange(fullKey, {
407
+ id: randomUUID().slice(0, 8),
408
+ key: entry.key,
409
+ namespace: entry.namespace,
410
+ action: 'expire',
411
+ oldValue: entry.value,
412
+ timestamp: now,
413
+ });
414
+ cleaned++;
415
+ }
416
+ }
417
+ return cleaned;
418
+ }
419
+ }
@@ -0,0 +1,2 @@
1
+ export * from './definitions.js';
2
+ export * from './handlers.impl.js';
@@ -0,0 +1,2 @@
1
+ export * from './definitions.js';
2
+ export * from './handlers.impl.js';
@@ -0,0 +1,57 @@
1
+ import type { MCPServerContext } from '../../domains/shared/registry.js';
2
+ import { SharedStateBoardHandlers } from '../../domains/shared-state-board/index.js';
3
+ type H = SharedStateBoardHandlers;
4
+ declare function ensure(ctx: MCPServerContext): H;
5
+ declare const manifest: {
6
+ kind: "domain-manifest";
7
+ version: 1;
8
+ domain: "shared-state-board";
9
+ depKey: "sharedStateBoardHandlers";
10
+ profiles: ("workflow" | "full")[];
11
+ ensure: typeof ensure;
12
+ registrations: {
13
+ tool: {
14
+ inputSchema: {
15
+ [x: string]: unknown;
16
+ type: "object";
17
+ properties?: {
18
+ [x: string]: object;
19
+ } | undefined;
20
+ required?: string[] | undefined;
21
+ };
22
+ name: string;
23
+ description?: string | undefined;
24
+ outputSchema?: {
25
+ [x: string]: unknown;
26
+ type: "object";
27
+ properties?: {
28
+ [x: string]: object;
29
+ } | undefined;
30
+ required?: string[] | undefined;
31
+ } | undefined;
32
+ annotations?: {
33
+ title?: string | undefined;
34
+ readOnlyHint?: boolean | undefined;
35
+ destructiveHint?: boolean | undefined;
36
+ idempotentHint?: boolean | undefined;
37
+ openWorldHint?: boolean | undefined;
38
+ } | undefined;
39
+ execution?: {
40
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
41
+ } | undefined;
42
+ _meta?: {
43
+ [x: string]: unknown;
44
+ } | undefined;
45
+ icons?: {
46
+ src: string;
47
+ mimeType?: string | undefined;
48
+ sizes?: string[] | undefined;
49
+ theme?: "light" | "dark" | undefined;
50
+ }[] | undefined;
51
+ title?: string | undefined;
52
+ };
53
+ domain: "shared-state-board";
54
+ bind: (deps: import("../../domains/shared/registry.js").ToolHandlerDeps) => (args: import("../../types.js").ToolArgs) => Promise<unknown>;
55
+ }[];
56
+ };
57
+ export default manifest;
@@ -0,0 +1,74 @@
1
+ import { bindByDepKey, toolLookup } from '../../domains/shared/registry.js';
2
+ import { sharedStateBoardTools } from '../../domains/shared-state-board/definitions.js';
3
+ import { SharedStateBoardHandlers } from '../../domains/shared-state-board/index.js';
4
+ const DOMAIN = 'shared-state-board';
5
+ const DEP_KEY = 'sharedStateBoardHandlers';
6
+ const t = toolLookup(sharedStateBoardTools);
7
+ const b = (invoke) => bindByDepKey(DEP_KEY, invoke);
8
+ function ensure(ctx) {
9
+ if (!ctx.sharedStateBoardHandlers) {
10
+ ctx.sharedStateBoardHandlers = new SharedStateBoardHandlers();
11
+ }
12
+ return ctx.sharedStateBoardHandlers;
13
+ }
14
+ const manifest = {
15
+ kind: 'domain-manifest',
16
+ version: 1,
17
+ domain: DOMAIN,
18
+ depKey: DEP_KEY,
19
+ profiles: ['workflow', 'full'],
20
+ ensure,
21
+ registrations: [
22
+ {
23
+ tool: t('state_board_set'),
24
+ domain: DOMAIN,
25
+ bind: b((h, a) => h.handleSet(a)),
26
+ },
27
+ {
28
+ tool: t('state_board_get'),
29
+ domain: DOMAIN,
30
+ bind: b((h, a) => h.handleGet(a)),
31
+ },
32
+ {
33
+ tool: t('state_board_delete'),
34
+ domain: DOMAIN,
35
+ bind: b((h, a) => h.handleDelete(a)),
36
+ },
37
+ {
38
+ tool: t('state_board_list'),
39
+ domain: DOMAIN,
40
+ bind: b((h, a) => h.handleList(a)),
41
+ },
42
+ {
43
+ tool: t('state_board_watch'),
44
+ domain: DOMAIN,
45
+ bind: b((h, a) => h.handleWatch(a)),
46
+ },
47
+ {
48
+ tool: t('state_board_unwatch'),
49
+ domain: DOMAIN,
50
+ bind: b((h, a) => h.handleUnwatch(a)),
51
+ },
52
+ {
53
+ tool: t('state_board_history'),
54
+ domain: DOMAIN,
55
+ bind: b((h, a) => h.handleHistory(a)),
56
+ },
57
+ {
58
+ tool: t('state_board_export'),
59
+ domain: DOMAIN,
60
+ bind: b((h, a) => h.handleExport(a)),
61
+ },
62
+ {
63
+ tool: t('state_board_import'),
64
+ domain: DOMAIN,
65
+ bind: b((h, a) => h.handleImport(a)),
66
+ },
67
+ {
68
+ tool: t('state_board_clear'),
69
+ domain: DOMAIN,
70
+ bind: b((h, a) => h.handleClear(a)),
71
+ },
72
+ ],
73
+ };
74
+ export default manifest;