synomem 0.1.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/AGENTS.md +42 -0
- package/ARCHITECTURE.md +126 -0
- package/CHANGELOG.md +35 -0
- package/CODE_OF_CONDUCT.md +29 -0
- package/CONTRIBUTING.md +48 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/SECURITY.md +117 -0
- package/dist/backend.d.ts +9 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +60 -0
- package/dist/backend.js.map +1 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1040 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +168 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1031 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +153 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +36 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +127 -0
- package/dist/credentials.js.map +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +60 -0
- package/dist/errors.js.map +1 -0
- package/dist/fs-utils.d.ts +7 -0
- package/dist/fs-utils.d.ts.map +1 -0
- package/dist/fs-utils.js +68 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/import.d.ts +655 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +226 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +698 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +45 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/oauth.d.ts +27 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +201 -0
- package/dist/oauth.js.map +1 -0
- package/dist/ports/projections.d.ts +6 -0
- package/dist/ports/projections.d.ts.map +1 -0
- package/dist/ports/projections.js +2 -0
- package/dist/ports/projections.js.map +1 -0
- package/dist/ports/repository.d.ts +31 -0
- package/dist/ports/repository.d.ts.map +1 -0
- package/dist/ports/repository.js +2 -0
- package/dist/ports/repository.js.map +1 -0
- package/dist/projections.d.ts +24 -0
- package/dist/projections.d.ts.map +1 -0
- package/dist/projections.js +531 -0
- package/dist/projections.js.map +1 -0
- package/dist/remote.d.ts +120 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +286 -0
- package/dist/remote.js.map +1 -0
- package/dist/schemas.d.ts +901 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +424 -0
- package/dist/schemas.js.map +1 -0
- package/dist/service.d.ts +126 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +2 -0
- package/dist/service.js.map +1 -0
- package/dist/skill-install.d.ts +34 -0
- package/dist/skill-install.d.ts.map +1 -0
- package/dist/skill-install.js +256 -0
- package/dist/skill-install.js.map +1 -0
- package/dist/storage.d.ts +94 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +1229 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +442 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/docs/cli.md +180 -0
- package/docs/examples.md +70 -0
- package/docs/mcp.md +96 -0
- package/docs/recovery.md +45 -0
- package/docs/releasing.md +61 -0
- package/docs/skill.md +93 -0
- package/docs/storage-format.md +83 -0
- package/openapi/synomem-v1.yaml +674 -0
- package/package.json +100 -0
- package/skills/synomem/SKILL.md +71 -0
- package/skills/synomem/agents/openai.yaml +8 -0
- package/skills/synomem/references/examples.md +28 -0
- package/src/backend.ts +91 -0
- package/src/cli.ts +1623 -0
- package/src/client.ts +1261 -0
- package/src/config.ts +170 -0
- package/src/credentials.ts +194 -0
- package/src/errors.ts +64 -0
- package/src/fs-utils.ts +85 -0
- package/src/import.ts +284 -0
- package/src/index.ts +61 -0
- package/src/mcp/index.ts +973 -0
- package/src/mcp-server.ts +46 -0
- package/src/oauth.ts +252 -0
- package/src/ports/projections.ts +6 -0
- package/src/ports/repository.ts +67 -0
- package/src/projections.ts +624 -0
- package/src/remote.ts +516 -0
- package/src/schemas.ts +457 -0
- package/src/service.ts +140 -0
- package/src/skill-install.ts +332 -0
- package/src/storage.ts +1603 -0
- package/src/types.ts +458 -0
- package/src/version.ts +16 -0
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { configuredServiceFactory } from '../backend.js';
|
|
6
|
+
import { SynomemError, asSynomemError } from '../errors.js';
|
|
7
|
+
import {
|
|
8
|
+
actorSchema,
|
|
9
|
+
agentIdSchema,
|
|
10
|
+
changesInputSchema,
|
|
11
|
+
createNoteSchema,
|
|
12
|
+
createTodoSchema,
|
|
13
|
+
giveKudosMcpSchema,
|
|
14
|
+
itemListInputSchema,
|
|
15
|
+
listInputSchema,
|
|
16
|
+
reviseNoteSchema,
|
|
17
|
+
sendMemoSchema,
|
|
18
|
+
updateTodoSchema,
|
|
19
|
+
} from '../schemas.js';
|
|
20
|
+
import { packageVersion } from '../version.js';
|
|
21
|
+
import type { SynomemService, SynomemServiceFactory } from '../service.js';
|
|
22
|
+
import type { ActorIdentity, SynomemClientOptions, KudosRecord } from '../types.js';
|
|
23
|
+
|
|
24
|
+
export interface SynomemMcpOptions extends Omit<SynomemClientOptions, 'actor'> {
|
|
25
|
+
actor: ActorIdentity;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const outputSchema = z.object({
|
|
29
|
+
ok: z.boolean(),
|
|
30
|
+
actor: actorSchema,
|
|
31
|
+
message: z.string(),
|
|
32
|
+
data: z.record(z.string(), z.unknown()).optional(),
|
|
33
|
+
errorCode: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function dataRecord(value: unknown): Record<string, unknown> {
|
|
37
|
+
const normalized = JSON.parse(JSON.stringify(value)) as unknown;
|
|
38
|
+
return typeof normalized === 'object' && normalized !== null && !Array.isArray(normalized)
|
|
39
|
+
? (normalized as Record<string, unknown>)
|
|
40
|
+
: { value: normalized };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function success(actor: ActorIdentity, message: string, data: unknown): CallToolResult {
|
|
44
|
+
const structuredContent = { ok: true, actor, message, data: dataRecord(data) };
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: message }],
|
|
47
|
+
structuredContent,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function failure(actor: ActorIdentity, error: unknown): CallToolResult {
|
|
52
|
+
const kudosError = asSynomemError(error);
|
|
53
|
+
const structuredContent = {
|
|
54
|
+
ok: false,
|
|
55
|
+
actor,
|
|
56
|
+
message: kudosError.message,
|
|
57
|
+
errorCode: kudosError.code,
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: 'text', text: `${kudosError.code}: ${kudosError.message}` }],
|
|
61
|
+
structuredContent,
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function canView(actor: ActorIdentity, record: KudosRecord): boolean {
|
|
67
|
+
if (record.event.visibility !== 'private') return true;
|
|
68
|
+
return (
|
|
69
|
+
actor.kind === 'human' ||
|
|
70
|
+
(actor.kind === 'agent' && record.event.recipientAgentId === actor.id) ||
|
|
71
|
+
(record.event.actor.kind === actor.kind && record.event.actor.id === actor.id)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function describeRecord(record: KudosRecord): string {
|
|
76
|
+
return `${record.event.recipientDisplayName} received “${record.event.title}” on ${record.event.createdAt.slice(0, 10)} (ID ${record.event.id}).`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SynomemMcpRuntime {
|
|
80
|
+
server: McpServer;
|
|
81
|
+
client: SynomemService;
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function createSynomemMcpServer(
|
|
86
|
+
options: SynomemMcpOptions,
|
|
87
|
+
serviceFactory: SynomemServiceFactory = configuredServiceFactory,
|
|
88
|
+
): Promise<SynomemMcpRuntime> {
|
|
89
|
+
const actor = actorSchema.parse(options.actor);
|
|
90
|
+
const client = serviceFactory({ ...options, actor });
|
|
91
|
+
await client.init();
|
|
92
|
+
const server = new McpServer(
|
|
93
|
+
{ name: 'synomem', version: packageVersion() },
|
|
94
|
+
{
|
|
95
|
+
instructions:
|
|
96
|
+
'Use Synomem for durable kudos, memos, notes, and todos. Store only necessary, factual content; never secrets or raw sensitive tool output. The server binds every write to its configured actor.',
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
server.registerTool(
|
|
101
|
+
'synomem_kudos_give',
|
|
102
|
+
{
|
|
103
|
+
title: 'Give kudos',
|
|
104
|
+
description:
|
|
105
|
+
'Use when a human explicitly requests recognition or a peer agent made a concrete, unusually useful contribution. State what the recipient did and why it mattered. Do not use for routine completion, generic politeness, self-congratulation, invented work, secrets, or raw sensitive tool output.',
|
|
106
|
+
inputSchema: giveKudosMcpSchema,
|
|
107
|
+
outputSchema,
|
|
108
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
109
|
+
},
|
|
110
|
+
async (input) => {
|
|
111
|
+
try {
|
|
112
|
+
const result = await client.kudos.give(input);
|
|
113
|
+
return success(
|
|
114
|
+
actor,
|
|
115
|
+
`${describeRecord(result.record)} ${result.deduplicated ? 'Deduplicated; the original event was returned.' : `Recorded by ${actor.displayName ?? actor.id}.`}`,
|
|
116
|
+
result,
|
|
117
|
+
);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return failure(actor, error);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
server.registerTool(
|
|
125
|
+
'synomem_kudos_list',
|
|
126
|
+
{
|
|
127
|
+
title: 'List kudos',
|
|
128
|
+
description:
|
|
129
|
+
'Return a context-safe page of compact kudos summaries, newest first. The default is 10 and maximum is 50. Use nextCursor for another page and kudos_get only for records whose full reason or evidence is needed.',
|
|
130
|
+
inputSchema: listInputSchema,
|
|
131
|
+
outputSchema,
|
|
132
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
133
|
+
},
|
|
134
|
+
async (input) => {
|
|
135
|
+
try {
|
|
136
|
+
const page = await client.kudos.list(input);
|
|
137
|
+
return success(
|
|
138
|
+
actor,
|
|
139
|
+
`Returned ${page.items.length} of ${page.total} visible kudos summaries${page.hasMore ? '; use nextCursor to continue' : ''}.`,
|
|
140
|
+
page,
|
|
141
|
+
);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return failure(actor, error);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
server.registerTool(
|
|
149
|
+
'synomem_kudos_changes',
|
|
150
|
+
{
|
|
151
|
+
title: 'Get kudos changes',
|
|
152
|
+
description:
|
|
153
|
+
'Return compact kudos changes after an opaque watermark. Persist nextCursor (or watermark when empty) and pass it as after on the next poll. The default is 20 and maximum is 100.',
|
|
154
|
+
inputSchema: changesInputSchema,
|
|
155
|
+
outputSchema,
|
|
156
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
157
|
+
},
|
|
158
|
+
async (input) => {
|
|
159
|
+
try {
|
|
160
|
+
const page = await client.kudos.changes(input);
|
|
161
|
+
return success(
|
|
162
|
+
actor,
|
|
163
|
+
`Returned ${page.items.length} visible kudos change(s)${page.hasMore ? '; use nextCursor to continue' : ''}.`,
|
|
164
|
+
page,
|
|
165
|
+
);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return failure(actor, error);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
server.registerTool(
|
|
173
|
+
'synomem_kudos_get',
|
|
174
|
+
{
|
|
175
|
+
title: 'Get kudos',
|
|
176
|
+
description: 'Use to inspect one kudos item and its acknowledgment or revocation state.',
|
|
177
|
+
inputSchema: z.object({ kudosId: z.string().length(26) }),
|
|
178
|
+
outputSchema,
|
|
179
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
180
|
+
},
|
|
181
|
+
async ({ kudosId }) => {
|
|
182
|
+
try {
|
|
183
|
+
const record = await client.kudos.get(kudosId);
|
|
184
|
+
if (!canView(actor, record))
|
|
185
|
+
throw new SynomemError(
|
|
186
|
+
'POLICY_FORBIDDEN',
|
|
187
|
+
'This private kudos item is not visible to the configured actor.',
|
|
188
|
+
);
|
|
189
|
+
return success(actor, describeRecord(record), { record });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return failure(actor, error);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
server.registerTool(
|
|
197
|
+
'synomem_kudos_acknowledge',
|
|
198
|
+
{
|
|
199
|
+
title: 'Acknowledge kudos',
|
|
200
|
+
description:
|
|
201
|
+
'Use when the configured recipient has reviewed received kudos. Acknowledgment records receipt and does not imply agreement with every detail.',
|
|
202
|
+
inputSchema: z.object({
|
|
203
|
+
kudosId: z.string().length(26),
|
|
204
|
+
note: z.string().trim().min(1).max(2000).optional(),
|
|
205
|
+
}),
|
|
206
|
+
outputSchema,
|
|
207
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
208
|
+
},
|
|
209
|
+
async (input) => {
|
|
210
|
+
try {
|
|
211
|
+
const record = await client.kudos.acknowledge(input);
|
|
212
|
+
return success(
|
|
213
|
+
actor,
|
|
214
|
+
`Acknowledged kudos ${record.event.id} as ${actor.displayName ?? actor.id}.`,
|
|
215
|
+
{
|
|
216
|
+
record,
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return failure(actor, error);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
server.registerTool(
|
|
226
|
+
'synomem_kudos_revoke',
|
|
227
|
+
{
|
|
228
|
+
title: 'Revoke kudos',
|
|
229
|
+
description:
|
|
230
|
+
'Use to record a revocation with a concrete reason. This preserves history and does not delete the original kudos.',
|
|
231
|
+
inputSchema: z.object({
|
|
232
|
+
kudosId: z.string().length(26),
|
|
233
|
+
reason: z.string().trim().min(1).max(2000),
|
|
234
|
+
administrative: z.boolean().default(false),
|
|
235
|
+
}),
|
|
236
|
+
outputSchema,
|
|
237
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
|
|
238
|
+
},
|
|
239
|
+
async (input) => {
|
|
240
|
+
try {
|
|
241
|
+
if (input.administrative && actor.kind !== 'human') {
|
|
242
|
+
throw new SynomemError(
|
|
243
|
+
'POLICY_FORBIDDEN',
|
|
244
|
+
'Only human actors can request administrative revocation.',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const record = await client.kudos.revoke(input);
|
|
248
|
+
return success(actor, `Revoked kudos ${record.event.id}; history was preserved.`, {
|
|
249
|
+
record,
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return failure(actor, error);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
server.registerTool(
|
|
258
|
+
'synomem_kudos_stats',
|
|
259
|
+
{
|
|
260
|
+
title: 'Kudos statistics',
|
|
261
|
+
description: 'Return aggregate recognition counts without exposing private message content.',
|
|
262
|
+
inputSchema: listInputSchema,
|
|
263
|
+
outputSchema,
|
|
264
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
265
|
+
},
|
|
266
|
+
async (input) => {
|
|
267
|
+
try {
|
|
268
|
+
const stats = await client.stats(input);
|
|
269
|
+
return success(actor, `Computed statistics for ${stats.total} kudos item(s).`, { stats });
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return failure(actor, error);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
server.registerTool(
|
|
277
|
+
'synomem_agent_create',
|
|
278
|
+
{
|
|
279
|
+
title: 'Create agent identity',
|
|
280
|
+
description:
|
|
281
|
+
'Administrative tool for creating a stable agent identity. Disabled by default so runtime agents cannot silently create identities.',
|
|
282
|
+
inputSchema: z.object({
|
|
283
|
+
id: agentIdSchema,
|
|
284
|
+
displayName: z.string().trim().min(1).max(200),
|
|
285
|
+
aliases: z.array(agentIdSchema).max(50).optional(),
|
|
286
|
+
description: z.string().trim().max(2000).optional(),
|
|
287
|
+
}),
|
|
288
|
+
outputSchema,
|
|
289
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
290
|
+
},
|
|
291
|
+
async (input) => {
|
|
292
|
+
try {
|
|
293
|
+
const capabilities = await client.capabilities();
|
|
294
|
+
if (!capabilities.administration.agentCreationViaMcp) {
|
|
295
|
+
throw new SynomemError(
|
|
296
|
+
'POLICY_FORBIDDEN',
|
|
297
|
+
'Agent creation via MCP is disabled by configuration.',
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const profile = await client.agents.create(input);
|
|
301
|
+
return success(actor, `Created agent ${profile.displayName} (${profile.id}).`, { profile });
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return failure(actor, error);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
server.registerTool(
|
|
309
|
+
'synomem_agent_list',
|
|
310
|
+
{
|
|
311
|
+
title: 'List agent identities',
|
|
312
|
+
description: 'List known stable agent identities and aliases. This is read-only.',
|
|
313
|
+
inputSchema: z.object({}),
|
|
314
|
+
outputSchema,
|
|
315
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
316
|
+
},
|
|
317
|
+
async () => {
|
|
318
|
+
try {
|
|
319
|
+
const agents = await client.agents.list();
|
|
320
|
+
return success(actor, `Found ${agents.length} agent identity or identities.`, { agents });
|
|
321
|
+
} catch (error) {
|
|
322
|
+
return failure(actor, error);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
server.registerTool(
|
|
328
|
+
'synomem_rebuild',
|
|
329
|
+
{
|
|
330
|
+
title: 'Rebuild projections',
|
|
331
|
+
description:
|
|
332
|
+
'Administrative operation that deterministically regenerates the SQLite current-state index, WINS.md, and inbox projections.',
|
|
333
|
+
inputSchema: z.object({}),
|
|
334
|
+
outputSchema,
|
|
335
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
336
|
+
},
|
|
337
|
+
async () => {
|
|
338
|
+
try {
|
|
339
|
+
const capabilities = await client.capabilities();
|
|
340
|
+
if (!capabilities.administration.rebuildViaMcp) {
|
|
341
|
+
throw new SynomemError(
|
|
342
|
+
'POLICY_FORBIDDEN',
|
|
343
|
+
'Projection rebuild via MCP is disabled by configuration.',
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
const result = await client.rebuild();
|
|
347
|
+
return success(actor, `Rebuilt ${result.generated.length} generated file(s).`, result);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return failure(actor, error);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
server.registerTool(
|
|
355
|
+
'synomem_doctor',
|
|
356
|
+
{
|
|
357
|
+
title: 'Run Synomem diagnostics',
|
|
358
|
+
description: 'Run safe, read-only database, projection, permission, and path diagnostics.',
|
|
359
|
+
inputSchema: z.object({}),
|
|
360
|
+
outputSchema,
|
|
361
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
362
|
+
},
|
|
363
|
+
async () => {
|
|
364
|
+
try {
|
|
365
|
+
const result = await client.doctor();
|
|
366
|
+
return success(actor, result.healthy ? 'Synomem is healthy.' : 'Synomem found problems.', {
|
|
367
|
+
result,
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return failure(actor, error);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
server.registerTool(
|
|
376
|
+
'synomem_list',
|
|
377
|
+
{
|
|
378
|
+
title: 'List Synomem items',
|
|
379
|
+
description:
|
|
380
|
+
'Discover a bounded page of compact kudos, memo, note, and todo summaries. Full bodies, reasons, evidence, descriptions, source, and metadata are omitted; use synomem_get for one selected item.',
|
|
381
|
+
inputSchema: itemListInputSchema,
|
|
382
|
+
outputSchema,
|
|
383
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
384
|
+
},
|
|
385
|
+
async (input) => {
|
|
386
|
+
try {
|
|
387
|
+
const page = await client.items.list(input);
|
|
388
|
+
return success(
|
|
389
|
+
actor,
|
|
390
|
+
`Returned ${page.items.length} of ${page.total} visible item summaries${page.hasMore ? '; use nextCursor to continue' : ''}.`,
|
|
391
|
+
page,
|
|
392
|
+
);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return failure(actor, error);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
server.registerTool(
|
|
400
|
+
'synomem_get',
|
|
401
|
+
{
|
|
402
|
+
title: 'Get one Synomem item',
|
|
403
|
+
description:
|
|
404
|
+
'Read the full authorized record for one explicitly selected kudos, memo, note, or todo ID.',
|
|
405
|
+
inputSchema: z.object({ itemId: z.string().length(26) }),
|
|
406
|
+
outputSchema,
|
|
407
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
408
|
+
},
|
|
409
|
+
async ({ itemId }) => {
|
|
410
|
+
try {
|
|
411
|
+
const record = await client.items.get(itemId);
|
|
412
|
+
return success(actor, `Retrieved item ${itemId}.`, { record });
|
|
413
|
+
} catch (error) {
|
|
414
|
+
return failure(actor, error);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
server.registerTool(
|
|
420
|
+
'synomem_changes',
|
|
421
|
+
{
|
|
422
|
+
title: 'Get Synomem changes',
|
|
423
|
+
description:
|
|
424
|
+
'Read bounded compact changes after an opaque saved watermark. Persist nextCursor for the next poll and do not drain history speculatively.',
|
|
425
|
+
inputSchema: changesInputSchema.extend({ kinds: itemListInputSchema.shape.kinds }),
|
|
426
|
+
outputSchema,
|
|
427
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
428
|
+
},
|
|
429
|
+
async (input) => {
|
|
430
|
+
try {
|
|
431
|
+
const page = await client.items.changes(input);
|
|
432
|
+
return success(
|
|
433
|
+
actor,
|
|
434
|
+
`Returned ${page.items.length} visible change(s)${page.hasMore ? '; use nextCursor to continue' : ''}.`,
|
|
435
|
+
page,
|
|
436
|
+
);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
return failure(actor, error);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
server.registerTool(
|
|
444
|
+
'synomem_inbox',
|
|
445
|
+
{
|
|
446
|
+
title: 'Review an agent inbox',
|
|
447
|
+
description:
|
|
448
|
+
'Return compact pending kudos, unread memos, and open todos for the configured agent. An agent may inspect only its own private items.',
|
|
449
|
+
inputSchema: z.object({
|
|
450
|
+
limit: z.number().int().min(1).max(50).default(10),
|
|
451
|
+
cursor: z.string().max(500).optional(),
|
|
452
|
+
}),
|
|
453
|
+
outputSchema,
|
|
454
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
455
|
+
},
|
|
456
|
+
async (input) => {
|
|
457
|
+
try {
|
|
458
|
+
if (actor.kind !== 'agent')
|
|
459
|
+
throw new SynomemError('POLICY_FORBIDDEN', 'Inbox review requires an agent-bound actor.');
|
|
460
|
+
const page = await client.items.list({
|
|
461
|
+
participantAgentId: actor.id,
|
|
462
|
+
limit: input.limit,
|
|
463
|
+
pending: true,
|
|
464
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
465
|
+
});
|
|
466
|
+
return success(actor, `Returned ${page.items.length} pending inbox item(s).`, page);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
return failure(actor, error);
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
server.registerTool(
|
|
474
|
+
'synomem_memo_send',
|
|
475
|
+
{
|
|
476
|
+
title: 'Send a memo',
|
|
477
|
+
description:
|
|
478
|
+
'Send a durable one-to-one memo to an agent or to the configured actor itself. Use for information that should survive the chat, not conversational chatter, transcripts, or secrets.',
|
|
479
|
+
inputSchema: sendMemoSchema,
|
|
480
|
+
outputSchema,
|
|
481
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
482
|
+
},
|
|
483
|
+
async (input) => {
|
|
484
|
+
try {
|
|
485
|
+
const result = await client.memos.send(input);
|
|
486
|
+
return success(
|
|
487
|
+
actor,
|
|
488
|
+
`${result.deduplicated ? 'Returned existing' : 'Sent'} memo “${result.record.event.subject}” to ${result.record.event.recipientDisplayName} (ID ${result.record.event.id}).`,
|
|
489
|
+
result,
|
|
490
|
+
);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return failure(actor, error);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
);
|
|
496
|
+
for (const operation of ['read', 'archive'] as const) {
|
|
497
|
+
server.registerTool(
|
|
498
|
+
`synomem_memo_${operation}`,
|
|
499
|
+
{
|
|
500
|
+
title: `${operation === 'read' ? 'Mark memo read' : 'Archive memo'}`,
|
|
501
|
+
description: `Use when the configured recipient should ${operation === 'read' ? 'record reviewing' : 'remove'} a memo${operation === 'archive' ? ' from its active inbox' : ''}.`,
|
|
502
|
+
inputSchema: z.object({
|
|
503
|
+
memoId: z.string().length(26),
|
|
504
|
+
idempotencyKey: z.string().max(200).optional(),
|
|
505
|
+
}),
|
|
506
|
+
outputSchema,
|
|
507
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
508
|
+
},
|
|
509
|
+
async (input) => {
|
|
510
|
+
try {
|
|
511
|
+
const record = await client.memos[operation](input);
|
|
512
|
+
return success(actor, `Memo ${record.event.id} is ${record.status}.`, { record });
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return failure(actor, error);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
server.registerTool(
|
|
521
|
+
'synomem_note_create',
|
|
522
|
+
{
|
|
523
|
+
title: 'Create a note',
|
|
524
|
+
description:
|
|
525
|
+
'Retain concise agent-owned knowledge for deliberate later retrieval. Agents may write only their own notes. Do not store secrets, unnecessary private content, or raw transcripts.',
|
|
526
|
+
inputSchema: createNoteSchema,
|
|
527
|
+
outputSchema,
|
|
528
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
529
|
+
},
|
|
530
|
+
async (input) => {
|
|
531
|
+
try {
|
|
532
|
+
const result = await client.notes.create(input);
|
|
533
|
+
return success(
|
|
534
|
+
actor,
|
|
535
|
+
`${result.deduplicated ? 'Returned existing' : 'Created'} note “${result.record.current.title}” (ID ${result.record.event.id}).`,
|
|
536
|
+
result,
|
|
537
|
+
);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
return failure(actor, error);
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
);
|
|
543
|
+
server.registerTool(
|
|
544
|
+
'synomem_note_revise',
|
|
545
|
+
{
|
|
546
|
+
title: 'Revise a note',
|
|
547
|
+
description:
|
|
548
|
+
'Append a complete new revision to an owned note. Pass the version last read; stale versions fail with REVISION_CONFLICT.',
|
|
549
|
+
inputSchema: reviseNoteSchema,
|
|
550
|
+
outputSchema,
|
|
551
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
552
|
+
},
|
|
553
|
+
async (input) => {
|
|
554
|
+
try {
|
|
555
|
+
const record = await client.notes.revise(input);
|
|
556
|
+
return success(
|
|
557
|
+
actor,
|
|
558
|
+
`Revised note ${record.event.id} to version ${record.current.version}.`,
|
|
559
|
+
{ record },
|
|
560
|
+
);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
return failure(actor, error);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
);
|
|
566
|
+
server.registerTool(
|
|
567
|
+
'synomem_note_archive',
|
|
568
|
+
{
|
|
569
|
+
title: 'Archive a note',
|
|
570
|
+
description: 'Archive an owned note while preserving its full revision history.',
|
|
571
|
+
inputSchema: z.object({
|
|
572
|
+
noteId: z.string().length(26),
|
|
573
|
+
idempotencyKey: z.string().max(200).optional(),
|
|
574
|
+
}),
|
|
575
|
+
outputSchema,
|
|
576
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
577
|
+
},
|
|
578
|
+
async (input) => {
|
|
579
|
+
try {
|
|
580
|
+
const record = await client.notes.archive(input);
|
|
581
|
+
return success(actor, `Archived note ${record.event.id}.`, { record });
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return failure(actor, error);
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
server.registerTool(
|
|
589
|
+
'synomem_todo_create',
|
|
590
|
+
{
|
|
591
|
+
title: 'Create a todo',
|
|
592
|
+
description:
|
|
593
|
+
'Create a concrete actionable todo assigned to an agent, optionally with a date-only or timezone-aware deadline. Do not use as a substitute for a memo when no action is required.',
|
|
594
|
+
inputSchema: createTodoSchema,
|
|
595
|
+
outputSchema,
|
|
596
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
597
|
+
},
|
|
598
|
+
async (input) => {
|
|
599
|
+
try {
|
|
600
|
+
const result = await client.todos.create(input);
|
|
601
|
+
return success(
|
|
602
|
+
actor,
|
|
603
|
+
`${result.deduplicated ? 'Returned existing' : 'Created'} todo “${result.record.current.title}” for ${result.record.event.assigneeDisplayName} (ID ${result.record.event.id}).`,
|
|
604
|
+
result,
|
|
605
|
+
);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return failure(actor, error);
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
server.registerTool(
|
|
612
|
+
'synomem_todo_update',
|
|
613
|
+
{
|
|
614
|
+
title: 'Update a todo',
|
|
615
|
+
description:
|
|
616
|
+
'Append an update to an open todo using the version last read. Stale versions fail rather than overwriting concurrent work.',
|
|
617
|
+
inputSchema: updateTodoSchema,
|
|
618
|
+
outputSchema,
|
|
619
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
620
|
+
},
|
|
621
|
+
async (input) => {
|
|
622
|
+
try {
|
|
623
|
+
const record = await client.todos.update(input);
|
|
624
|
+
return success(
|
|
625
|
+
actor,
|
|
626
|
+
`Updated todo ${record.event.id} to version ${record.current.version}.`,
|
|
627
|
+
{ record },
|
|
628
|
+
);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return failure(actor, error);
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
);
|
|
634
|
+
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel'] as const) {
|
|
635
|
+
const inputSchema = z.object({
|
|
636
|
+
todoId: z.string().length(26),
|
|
637
|
+
note: z.string().trim().min(1).max(2000).optional(),
|
|
638
|
+
reason: z.string().trim().min(1).max(2000).optional(),
|
|
639
|
+
idempotencyKey: z.string().max(200).optional(),
|
|
640
|
+
});
|
|
641
|
+
server.registerTool(
|
|
642
|
+
`synomem_todo_${operation}`,
|
|
643
|
+
{
|
|
644
|
+
title: `${operation[0]!.toUpperCase()}${operation.slice(1)} a todo`,
|
|
645
|
+
description: `${operation[0]!.toUpperCase()}${operation.slice(1)} an authorized todo by appending a lifecycle event; history is never deleted.`,
|
|
646
|
+
inputSchema,
|
|
647
|
+
outputSchema,
|
|
648
|
+
annotations: {
|
|
649
|
+
readOnlyHint: false,
|
|
650
|
+
destructiveHint: operation === 'cancel',
|
|
651
|
+
idempotentHint: true,
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
async (input) => {
|
|
655
|
+
try {
|
|
656
|
+
const record =
|
|
657
|
+
operation === 'accept'
|
|
658
|
+
? await client.todos.accept({
|
|
659
|
+
todoId: input.todoId,
|
|
660
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
661
|
+
})
|
|
662
|
+
: operation === 'reject'
|
|
663
|
+
? await client.todos.reject({
|
|
664
|
+
todoId: input.todoId,
|
|
665
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
666
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
667
|
+
})
|
|
668
|
+
: operation === 'complete'
|
|
669
|
+
? await client.todos.complete({
|
|
670
|
+
todoId: input.todoId,
|
|
671
|
+
...(input.note ? { note: input.note } : {}),
|
|
672
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
673
|
+
})
|
|
674
|
+
: operation === 'cancel'
|
|
675
|
+
? await client.todos.cancel({
|
|
676
|
+
todoId: input.todoId,
|
|
677
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
678
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
679
|
+
})
|
|
680
|
+
: await client.todos.reopen({
|
|
681
|
+
todoId: input.todoId,
|
|
682
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
683
|
+
});
|
|
684
|
+
return success(actor, `Todo ${record.event.id} is ${record.status}.`, { record });
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return failure(actor, error);
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
server.registerResource(
|
|
693
|
+
'agents',
|
|
694
|
+
'synomem://agents',
|
|
695
|
+
{
|
|
696
|
+
title: 'Agent identities',
|
|
697
|
+
description: 'Known Synomem identities',
|
|
698
|
+
mimeType: 'application/json',
|
|
699
|
+
},
|
|
700
|
+
async (uri) => ({
|
|
701
|
+
contents: [
|
|
702
|
+
{
|
|
703
|
+
uri: uri.href,
|
|
704
|
+
mimeType: 'application/json',
|
|
705
|
+
text: JSON.stringify(await client.agents.list(), null, 2),
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
}),
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
server.registerResource(
|
|
712
|
+
'agent-profile',
|
|
713
|
+
new ResourceTemplate('synomem://agents/{agentId}/profile', { list: undefined }),
|
|
714
|
+
{
|
|
715
|
+
title: 'Agent profile',
|
|
716
|
+
description: 'One stable agent profile',
|
|
717
|
+
mimeType: 'application/json',
|
|
718
|
+
},
|
|
719
|
+
async (uri, { agentId }) => ({
|
|
720
|
+
contents: [
|
|
721
|
+
{
|
|
722
|
+
uri: uri.href,
|
|
723
|
+
mimeType: 'application/json',
|
|
724
|
+
text: JSON.stringify(await client.agents.get(String(agentId)), null, 2),
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
}),
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
server.registerResource(
|
|
731
|
+
'agent-wins',
|
|
732
|
+
new ResourceTemplate('synomem://agents/{agentId}/wins', { list: undefined }),
|
|
733
|
+
{
|
|
734
|
+
title: 'Agent wins',
|
|
735
|
+
description: 'Ten most recent visible, active kudos summaries for one agent',
|
|
736
|
+
mimeType: 'application/json',
|
|
737
|
+
},
|
|
738
|
+
async (uri, { agentId }) => {
|
|
739
|
+
const page = await client.kudos.list({
|
|
740
|
+
recipientAgentId: String(agentId),
|
|
741
|
+
revoked: false,
|
|
742
|
+
limit: 10,
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
contents: [
|
|
746
|
+
{
|
|
747
|
+
uri: uri.href,
|
|
748
|
+
mimeType: 'application/json',
|
|
749
|
+
text: JSON.stringify(page, null, 2),
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
},
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
server.registerResource(
|
|
757
|
+
'agent-inbox',
|
|
758
|
+
new ResourceTemplate('synomem://agents/{agentId}/inbox', { list: undefined }),
|
|
759
|
+
{
|
|
760
|
+
title: 'Agent inbox',
|
|
761
|
+
description: 'Ten recent visible pending kudos, memos, and todos for one agent',
|
|
762
|
+
mimeType: 'application/json',
|
|
763
|
+
},
|
|
764
|
+
async (uri, { agentId }) => {
|
|
765
|
+
const requested = String(agentId);
|
|
766
|
+
const profile = await client.agents.get(requested);
|
|
767
|
+
if (actor.kind === 'agent' && profile.id !== actor.id) {
|
|
768
|
+
throw new SynomemError(
|
|
769
|
+
'POLICY_FORBIDDEN',
|
|
770
|
+
'An agent may read only its own inbox resource.',
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
const page = await client.items.list({
|
|
774
|
+
participantAgentId: profile.id,
|
|
775
|
+
limit: 10,
|
|
776
|
+
});
|
|
777
|
+
page.items = page.items.filter((item) =>
|
|
778
|
+
['unacknowledged', 'unread', 'open'].includes(item.status),
|
|
779
|
+
);
|
|
780
|
+
return {
|
|
781
|
+
contents: [
|
|
782
|
+
{
|
|
783
|
+
uri: uri.href,
|
|
784
|
+
mimeType: 'application/json',
|
|
785
|
+
text: JSON.stringify(page, null, 2),
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
};
|
|
789
|
+
},
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
server.registerResource(
|
|
793
|
+
'event',
|
|
794
|
+
new ResourceTemplate('synomem://events/{eventId}', { list: undefined }),
|
|
795
|
+
{
|
|
796
|
+
title: 'Synomem event',
|
|
797
|
+
description: 'One visible canonical event',
|
|
798
|
+
mimeType: 'application/json',
|
|
799
|
+
},
|
|
800
|
+
async (uri, { eventId }) => {
|
|
801
|
+
const event = await client.getCanonicalEvent(String(eventId));
|
|
802
|
+
if (!event) throw new SynomemError('ITEM_NOT_FOUND', `Unknown event: ${String(eventId)}`);
|
|
803
|
+
if (!event.type.startsWith('agent.')) await client.items.get(event.aggregateId);
|
|
804
|
+
return {
|
|
805
|
+
contents: [
|
|
806
|
+
{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(event, null, 2) },
|
|
807
|
+
],
|
|
808
|
+
};
|
|
809
|
+
},
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
server.registerResource(
|
|
813
|
+
'item',
|
|
814
|
+
new ResourceTemplate('synomem://items/{itemId}', { list: undefined }),
|
|
815
|
+
{
|
|
816
|
+
title: 'Synomem item',
|
|
817
|
+
description: 'One authorized full item record',
|
|
818
|
+
mimeType: 'application/json',
|
|
819
|
+
},
|
|
820
|
+
async (uri, { itemId }) => ({
|
|
821
|
+
contents: [
|
|
822
|
+
{
|
|
823
|
+
uri: uri.href,
|
|
824
|
+
mimeType: 'application/json',
|
|
825
|
+
text: JSON.stringify(await client.items.get(String(itemId)), null, 2),
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
}),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
server.registerPrompt(
|
|
832
|
+
'synomem_recognize_contribution',
|
|
833
|
+
{
|
|
834
|
+
title: 'Recognize a contribution',
|
|
835
|
+
description: 'Draft concrete, evidence-based kudos without inventing accomplishments.',
|
|
836
|
+
argsSchema: {
|
|
837
|
+
recipient: z.string().describe('Known agent ID'),
|
|
838
|
+
contribution: z.string().describe('Observed contribution and why it mattered'),
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
({ recipient, contribution }) => ({
|
|
842
|
+
messages: [
|
|
843
|
+
{
|
|
844
|
+
role: 'user',
|
|
845
|
+
content: {
|
|
846
|
+
type: 'text',
|
|
847
|
+
text: `Prepare specific kudos for ${recipient}. Describe what the agent did, why it mattered, and only evidence actually observed: ${contribution}. Do not invent details or include secrets.`,
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
}),
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
server.registerPrompt(
|
|
855
|
+
'synomem_review_kudos_inbox',
|
|
856
|
+
{
|
|
857
|
+
title: 'Review kudos inbox',
|
|
858
|
+
description:
|
|
859
|
+
'Review the configured agent’s unacknowledged kudos before acknowledging any item.',
|
|
860
|
+
},
|
|
861
|
+
() => ({
|
|
862
|
+
messages: [
|
|
863
|
+
{
|
|
864
|
+
role: 'user',
|
|
865
|
+
content: {
|
|
866
|
+
type: 'text',
|
|
867
|
+
text: `Review the kudos inbox for ${actor.displayName ?? actor.id}. Summarize each concrete contribution. Acknowledge only after it has been reviewed; acknowledgment records receipt, not blanket agreement.`,
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
],
|
|
871
|
+
}),
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
server.registerPrompt(
|
|
875
|
+
'synomem_summarize_agent_wins',
|
|
876
|
+
{
|
|
877
|
+
title: 'Summarize agent wins',
|
|
878
|
+
description: 'Summarize supported recognition without embellishment.',
|
|
879
|
+
argsSchema: { agentId: agentIdSchema },
|
|
880
|
+
},
|
|
881
|
+
({ agentId }) => ({
|
|
882
|
+
messages: [
|
|
883
|
+
{
|
|
884
|
+
role: 'user',
|
|
885
|
+
content: {
|
|
886
|
+
type: 'text',
|
|
887
|
+
text: `Summarize active, visible wins for ${agentId}. Stay factual, distinguish acknowledgment state, and do not infer accomplishments absent from the records.`,
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
],
|
|
891
|
+
}),
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
server.registerPrompt(
|
|
895
|
+
'synomem_send_durable_memo',
|
|
896
|
+
{
|
|
897
|
+
title: 'Send a durable memo',
|
|
898
|
+
description: 'Turn necessary inter-agent information into a concise, secret-free memo.',
|
|
899
|
+
argsSchema: { recipient: agentIdSchema, information: z.string() },
|
|
900
|
+
},
|
|
901
|
+
({ recipient, information }) => ({
|
|
902
|
+
messages: [
|
|
903
|
+
{
|
|
904
|
+
role: 'user',
|
|
905
|
+
content: {
|
|
906
|
+
type: 'text',
|
|
907
|
+
text: `Prepare a concise one-to-one memo for ${recipient} containing only the durable information needed later: ${information}. Do not include secrets, raw tool output, or unnecessary transcript content.`,
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
],
|
|
911
|
+
}),
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
server.registerPrompt(
|
|
915
|
+
'synomem_capture_agent_note',
|
|
916
|
+
{
|
|
917
|
+
title: 'Capture agent memory',
|
|
918
|
+
description: 'Turn reusable knowledge into a concise owner-private note.',
|
|
919
|
+
argsSchema: { knowledge: z.string() },
|
|
920
|
+
},
|
|
921
|
+
({ knowledge }) => ({
|
|
922
|
+
messages: [
|
|
923
|
+
{
|
|
924
|
+
role: 'user',
|
|
925
|
+
content: {
|
|
926
|
+
type: 'text',
|
|
927
|
+
text: `Prepare an owner-private Synomem note containing concise reusable knowledge: ${knowledge}. Distinguish verified facts from uncertainty and omit secrets.`,
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
],
|
|
931
|
+
}),
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
server.registerPrompt(
|
|
935
|
+
'synomem_create_actionable_todo',
|
|
936
|
+
{
|
|
937
|
+
title: 'Create an actionable todo',
|
|
938
|
+
description:
|
|
939
|
+
'Turn requested work into a concrete assigned action without inventing deadlines.',
|
|
940
|
+
argsSchema: { assignee: agentIdSchema, action: z.string() },
|
|
941
|
+
},
|
|
942
|
+
({ assignee, action }) => ({
|
|
943
|
+
messages: [
|
|
944
|
+
{
|
|
945
|
+
role: 'user',
|
|
946
|
+
content: {
|
|
947
|
+
type: 'text',
|
|
948
|
+
text: `Prepare a concrete todo assigned to ${assignee}: ${action}. Preserve a date-only deadline as a date, require timezone data for a timed deadline, and do not invent missing timing.`,
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
],
|
|
952
|
+
}),
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
return {
|
|
956
|
+
server,
|
|
957
|
+
client,
|
|
958
|
+
async close() {
|
|
959
|
+
await server.close();
|
|
960
|
+
await client.close();
|
|
961
|
+
},
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
export async function startMcpServer(
|
|
966
|
+
options: SynomemMcpOptions,
|
|
967
|
+
serviceFactory: SynomemServiceFactory = configuredServiceFactory,
|
|
968
|
+
): Promise<SynomemMcpRuntime> {
|
|
969
|
+
const runtime = await createSynomemMcpServer(options, serviceFactory);
|
|
970
|
+
const transport = new StdioServerTransport();
|
|
971
|
+
await runtime.server.connect(transport);
|
|
972
|
+
return runtime;
|
|
973
|
+
}
|