@olivaresai/alma-mcp 1.3.1 → 1.3.3

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 (3) hide show
  1. package/README.md +28 -3
  2. package/dist/index.js +679 -4
  3. package/package.json +26 -12
package/README.md CHANGED
@@ -47,11 +47,11 @@ Then use `"command": "alma-mcp"` instead of `npx`.
47
47
  | Variable | Required | Description |
48
48
  |----------|----------|-------------|
49
49
  | `ALMA_API_KEY` | Yes | Your Alma API key ([get one here](https://alma.olivares.ai/settings)) |
50
- | `ALMA_BASE_URL` | No | API base URL (default: `https://alma.olivares.ai/api/v1`) |
50
+ | `ALMA_API_URL` | No | API base URL (default: `https://alma.olivares.ai/api/v1`). `ALMA_BASE_URL` is accepted as an alias. |
51
51
  | `ALMA_ENVIRONMENT_ID` | No | Default environment ID |
52
52
  | `ALMA_DEBUG` | No | Enable debug logging |
53
53
 
54
- ## Tools (21)
54
+ ## Tools (35)
55
55
 
56
56
  ### Context
57
57
 
@@ -94,7 +94,31 @@ Then use `"command": "alma-mcp"` instead of `npx`.
94
94
  | `alma_list_environments` | List all environments |
95
95
  | `alma_create_environment` | Create a new environment |
96
96
 
97
- ## Resources (9)
97
+ ### Cowork (Collaborative Workspaces)
98
+
99
+ | Tool | Description |
100
+ |------|-------------|
101
+ | `cowork_list_workspaces` | List all collaborative workspaces |
102
+ | `cowork_create_workspace` | Create a new collaborative workspace |
103
+ | `cowork_list_files` | List all files in a workspace |
104
+ | `cowork_read_file` | Read the content of a workspace file |
105
+ | `cowork_write_file` | Write or update a file in a workspace |
106
+ | `cowork_search` | Search across all files in a workspace |
107
+ | `cowork_execute_skill` | Run an AI skill on a file (explain, refactor, review, test, fix, document, search, commit) |
108
+ | `cowork_apply_diff` | Apply a pending operation diff to a workspace file |
109
+
110
+ ### Video
111
+
112
+ | Tool | Description |
113
+ |------|-------------|
114
+ | `video_generate` | Start an AI video generation job |
115
+ | `video_check_job` | Check the status of a video generation job |
116
+ | `video_list_projects` | List all video projects |
117
+ | `video_create_project` | Create a new video project |
118
+ | `video_plan_scenes` | Use AI to plan scenes for a video project |
119
+ | `video_stitch` | Stitch multiple video scenes into a single final video |
120
+
121
+ ## Resources (10)
98
122
 
99
123
  | Resource URI | Description |
100
124
  |-------------|-------------|
@@ -104,6 +128,7 @@ Then use `"command": "alma-mcp"` instead of `npx`.
104
128
  | `alma://environments` | All available environments |
105
129
  | `alma://conversations` | Recent conversations (last 20) |
106
130
  | `alma://budget` | Token budget status |
131
+ | `alma://video-budget` | Video generation credits and usage |
107
132
  | `alma://blocks` | All soul memory blocks |
108
133
  | `alma://episodes` | Recent episodes (last 20) |
109
134
  | `alma://procedures` | All stored procedures (limit 50) |
package/dist/index.js CHANGED
@@ -18,10 +18,40 @@ function loadConfig() {
18
18
  console.error("[alma-mcp] ALMA_API_URL must use HTTPS for non-localhost connections. Got:", cleanUrl);
19
19
  process.exit(1);
20
20
  }
21
+ if (!cleanUrl.startsWith("https://") && process.env.NODE_ENV !== "development" && process.env.ALMA_DEV !== "1") {
22
+ console.error(
23
+ `[alma-mcp] WARNING: ALMA_API_URL points at ${cleanUrl}. This is only correct for local development. For Alma cloud use https://alma.olivares.ai/api/v1 (or set NODE_ENV=development to silence this warning).`
24
+ );
25
+ }
21
26
  return { baseUrl: cleanUrl, apiKey, environmentId };
22
27
  }
23
28
 
24
29
  // src/client.ts
30
+ function formatApiError(method, path, status, detail, code) {
31
+ const tail = detail ? ` ${detail}` : "";
32
+ if (status === 401) {
33
+ return `Alma rejected the API key. Generate a new one at https://alma.olivares.ai/settings (API Keys) and set ALMA_API_KEY in your MCP config.${tail}`;
34
+ }
35
+ if (status === 402) {
36
+ if (code === "BUDGET_EXHAUSTED" || /budget/i.test(detail)) {
37
+ return `Your Alma weekly AI budget is exhausted. Top up at https://alma.olivares.ai/settings/billing or wait until Monday 00:00 UTC for the budget to reset.${tail}`;
38
+ }
39
+ return `This action requires an active Alma plan or additional credits. Manage your subscription at https://alma.olivares.ai/settings/billing.${tail}`;
40
+ }
41
+ if (status === 403) {
42
+ if (code === "PLAN_API_RESTRICTED" || /api/i.test(detail)) {
43
+ return `The Alma API is available on the Max plan. Upgrade at https://alma.olivares.ai/settings/billing.${tail}`;
44
+ }
45
+ if (code === "BYOK_REQUIRED_PLAN") {
46
+ return `BYOK (bring your own keys) is available on the Max plan. Upgrade at https://alma.olivares.ai/settings/billing.${tail}`;
47
+ }
48
+ return `Forbidden \u2014 your plan does not include this feature. See https://alma.olivares.ai/settings/billing.${tail}`;
49
+ }
50
+ if (status === 429) {
51
+ return `Alma rate limit hit. Slow down and retry in a minute.${tail}`;
52
+ }
53
+ return `Alma API ${method} ${path} failed (${status}):${tail}`;
54
+ }
25
55
  var AlmaClient = class _AlmaClient {
26
56
  constructor(config2) {
27
57
  this.config = config2;
@@ -83,12 +113,14 @@ var AlmaClient = class _AlmaClient {
83
113
  if (!res.ok) {
84
114
  const text = await res.text().catch(() => "");
85
115
  let detail = text;
116
+ let code;
86
117
  try {
87
118
  const json = JSON.parse(text);
88
119
  detail = json.error?.message ?? json.message ?? text;
120
+ code = json.error?.code ?? json.code;
89
121
  } catch {
90
122
  }
91
- throw new Error(`Alma API ${method} ${path} failed (${res.status}): ${detail}`);
123
+ throw new Error(formatApiError(method, path, res.status, detail, code));
92
124
  }
93
125
  return res.json();
94
126
  }
@@ -185,7 +217,15 @@ var AlmaClient = class _AlmaClient {
185
217
  );
186
218
  if (!res.ok) {
187
219
  const text2 = await res.text().catch(() => "");
188
- throw new Error(`Chat send failed (${res.status}): ${text2}`);
220
+ let detail = text2;
221
+ let code;
222
+ try {
223
+ const json = JSON.parse(text2);
224
+ detail = json.error?.message ?? json.message ?? text2;
225
+ code = json.error?.code ?? json.code;
226
+ } catch {
227
+ }
228
+ throw new Error(formatApiError("POST", `/chat/conversations/${conversationId}/messages`, res.status, detail, code));
189
229
  }
190
230
  const text = await res.text();
191
231
  const rawEvents = text.split("\n\n");
@@ -328,6 +368,87 @@ var AlmaClient = class _AlmaClient {
328
368
  async createProcedure(params) {
329
369
  return this.request("POST", "/procedures", params);
330
370
  }
371
+ // ─── Cowork (Collaborative Workspaces) ─────────────
372
+ async listWorkspaces() {
373
+ return this.request("GET", "/cowork/workspaces");
374
+ }
375
+ async createWorkspace(params) {
376
+ return this.request("POST", "/cowork/workspaces", params);
377
+ }
378
+ async listWorkspaceFiles(workspaceId) {
379
+ return this.request(
380
+ "GET",
381
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/files`
382
+ );
383
+ }
384
+ async readWorkspaceFile(workspaceId, filePath) {
385
+ const qs = new URLSearchParams({ path: filePath });
386
+ return this.request(
387
+ "GET",
388
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/files/read?${qs}`
389
+ );
390
+ }
391
+ async writeWorkspaceFile(workspaceId, filePath, content) {
392
+ return this.request(
393
+ "POST",
394
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/files/write`,
395
+ { path: filePath, content }
396
+ );
397
+ }
398
+ async searchWorkspace(workspaceId, query) {
399
+ const qs = new URLSearchParams({ q: query });
400
+ return this.request(
401
+ "GET",
402
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/search?${qs}`
403
+ );
404
+ }
405
+ async executeSkill(workspaceId, params) {
406
+ return this.request(
407
+ "POST",
408
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/skills`,
409
+ params
410
+ );
411
+ }
412
+ async applyDiff(workspaceId, operationId) {
413
+ return this.request(
414
+ "POST",
415
+ `/cowork/workspaces/${encodeURIComponent(workspaceId)}/apply`,
416
+ { operationId }
417
+ );
418
+ }
419
+ // ─── Video ────────────────────────────────────────
420
+ async generateVideo(params) {
421
+ return this.request("POST", "/video/generate", params);
422
+ }
423
+ async checkVideoJob(jobId) {
424
+ return this.request(
425
+ "GET",
426
+ `/video/jobs/${encodeURIComponent(jobId)}`
427
+ );
428
+ }
429
+ async listVideoProjects() {
430
+ return this.request("GET", "/video/projects");
431
+ }
432
+ async createVideoProject(params) {
433
+ return this.request("POST", "/video/projects", params);
434
+ }
435
+ async planVideoScenes(projectId, params) {
436
+ return this.request(
437
+ "POST",
438
+ `/video/projects/${encodeURIComponent(projectId)}/plan`,
439
+ params
440
+ );
441
+ }
442
+ async stitchVideo(projectId, jobIds) {
443
+ return this.request(
444
+ "POST",
445
+ `/video/projects/${encodeURIComponent(projectId)}/stitch`,
446
+ { jobIds }
447
+ );
448
+ }
449
+ async getVideoBudget() {
450
+ return this.request("GET", "/video/budget");
451
+ }
331
452
  };
332
453
 
333
454
  // src/tools/assemble.ts
@@ -1232,12 +1353,470 @@ ${JSON.stringify(result.memory, null, 2)}`
1232
1353
  );
1233
1354
  }
1234
1355
 
1356
+ // src/tools/cowork.ts
1357
+ import { z as z18 } from "zod";
1358
+ function registerCoworkTools(server2, client2) {
1359
+ server2.registerTool(
1360
+ "cowork_list_workspaces",
1361
+ {
1362
+ title: "List Workspaces",
1363
+ description: "List all collaborative workspaces available to the user. Returns workspace IDs, names, and metadata.",
1364
+ inputSchema: {}
1365
+ },
1366
+ async () => {
1367
+ try {
1368
+ const result = await client2.listWorkspaces();
1369
+ if (result.workspaces.length === 0) {
1370
+ return {
1371
+ content: [{ type: "text", text: "No workspaces found." }]
1372
+ };
1373
+ }
1374
+ return {
1375
+ content: [
1376
+ {
1377
+ type: "text",
1378
+ text: JSON.stringify(result, null, 2)
1379
+ }
1380
+ ]
1381
+ };
1382
+ } catch (err) {
1383
+ const message = err instanceof Error ? err.message : "Unknown error";
1384
+ return {
1385
+ content: [{ type: "text", text: `Error: ${message}` }],
1386
+ isError: true
1387
+ };
1388
+ }
1389
+ }
1390
+ );
1391
+ server2.registerTool(
1392
+ "cowork_create_workspace",
1393
+ {
1394
+ title: "Create Workspace",
1395
+ description: "Create a new collaborative workspace. Workspaces hold files that Alma can read, write, and operate on.",
1396
+ inputSchema: {
1397
+ name: z18.string().min(1).max(200).describe("Name for the workspace"),
1398
+ description: z18.string().max(1e3).optional().describe("Optional description of the workspace purpose"),
1399
+ sourceType: z18.string().optional().describe('Source type for the workspace (e.g. "blank", "template", "import")')
1400
+ }
1401
+ },
1402
+ async ({ name, description, sourceType }) => {
1403
+ try {
1404
+ const result = await client2.createWorkspace({ name, description, sourceType });
1405
+ return {
1406
+ content: [
1407
+ {
1408
+ type: "text",
1409
+ text: `Workspace created successfully.
1410
+ ${JSON.stringify(result.workspace, null, 2)}`
1411
+ }
1412
+ ]
1413
+ };
1414
+ } catch (err) {
1415
+ const message = err instanceof Error ? err.message : "Unknown error";
1416
+ return {
1417
+ content: [{ type: "text", text: `Error: ${message}` }],
1418
+ isError: true
1419
+ };
1420
+ }
1421
+ }
1422
+ );
1423
+ server2.registerTool(
1424
+ "cowork_list_files",
1425
+ {
1426
+ title: "List Workspace Files",
1427
+ description: "List all files in a collaborative workspace. Returns file paths, sizes, and metadata.",
1428
+ inputSchema: {
1429
+ workspaceId: z18.string().min(1).describe("The workspace ID to list files from")
1430
+ }
1431
+ },
1432
+ async ({ workspaceId }) => {
1433
+ try {
1434
+ const result = await client2.listWorkspaceFiles(workspaceId);
1435
+ if (result.files.length === 0) {
1436
+ return {
1437
+ content: [{ type: "text", text: "No files found in this workspace." }]
1438
+ };
1439
+ }
1440
+ return {
1441
+ content: [
1442
+ {
1443
+ type: "text",
1444
+ text: JSON.stringify(result, null, 2)
1445
+ }
1446
+ ]
1447
+ };
1448
+ } catch (err) {
1449
+ const message = err instanceof Error ? err.message : "Unknown error";
1450
+ return {
1451
+ content: [{ type: "text", text: `Error: ${message}` }],
1452
+ isError: true
1453
+ };
1454
+ }
1455
+ }
1456
+ );
1457
+ server2.registerTool(
1458
+ "cowork_read_file",
1459
+ {
1460
+ title: "Read Workspace File",
1461
+ description: "Read the content of a file in a collaborative workspace. Returns the file content and metadata.",
1462
+ inputSchema: {
1463
+ workspaceId: z18.string().min(1).describe("The workspace ID containing the file"),
1464
+ filePath: z18.string().min(1).describe("Path of the file within the workspace")
1465
+ }
1466
+ },
1467
+ async ({ workspaceId, filePath }) => {
1468
+ try {
1469
+ const result = await client2.readWorkspaceFile(workspaceId, filePath);
1470
+ return {
1471
+ content: [
1472
+ {
1473
+ type: "text",
1474
+ text: JSON.stringify(result.file, null, 2)
1475
+ }
1476
+ ]
1477
+ };
1478
+ } catch (err) {
1479
+ const message = err instanceof Error ? err.message : "Unknown error";
1480
+ return {
1481
+ content: [{ type: "text", text: `Error: ${message}` }],
1482
+ isError: true
1483
+ };
1484
+ }
1485
+ }
1486
+ );
1487
+ server2.registerTool(
1488
+ "cowork_write_file",
1489
+ {
1490
+ title: "Write Workspace File",
1491
+ description: "Write or update a file in a collaborative workspace. Creates the file if it does not exist.",
1492
+ inputSchema: {
1493
+ workspaceId: z18.string().min(1).describe("The workspace ID to write the file in"),
1494
+ filePath: z18.string().min(1).describe("Path of the file within the workspace"),
1495
+ content: z18.string().describe("The content to write to the file")
1496
+ }
1497
+ },
1498
+ async ({ workspaceId, filePath, content }) => {
1499
+ try {
1500
+ const result = await client2.writeWorkspaceFile(workspaceId, filePath, content);
1501
+ return {
1502
+ content: [
1503
+ {
1504
+ type: "text",
1505
+ text: `File written successfully.
1506
+ ${JSON.stringify(result.file, null, 2)}`
1507
+ }
1508
+ ]
1509
+ };
1510
+ } catch (err) {
1511
+ const message = err instanceof Error ? err.message : "Unknown error";
1512
+ return {
1513
+ content: [{ type: "text", text: `Error: ${message}` }],
1514
+ isError: true
1515
+ };
1516
+ }
1517
+ }
1518
+ );
1519
+ server2.registerTool(
1520
+ "cowork_search",
1521
+ {
1522
+ title: "Search Workspace",
1523
+ description: "Search across all files in a collaborative workspace. Returns matching files and content snippets.",
1524
+ inputSchema: {
1525
+ workspaceId: z18.string().min(1).describe("The workspace ID to search in"),
1526
+ query: z18.string().min(1).describe("Search query string")
1527
+ }
1528
+ },
1529
+ async ({ workspaceId, query }) => {
1530
+ try {
1531
+ const result = await client2.searchWorkspace(workspaceId, query);
1532
+ if (result.results.length === 0) {
1533
+ return {
1534
+ content: [{ type: "text", text: `No results found for: ${query}` }]
1535
+ };
1536
+ }
1537
+ return {
1538
+ content: [
1539
+ {
1540
+ type: "text",
1541
+ text: JSON.stringify(result, null, 2)
1542
+ }
1543
+ ]
1544
+ };
1545
+ } catch (err) {
1546
+ const message = err instanceof Error ? err.message : "Unknown error";
1547
+ return {
1548
+ content: [{ type: "text", text: `Error: ${message}` }],
1549
+ isError: true
1550
+ };
1551
+ }
1552
+ }
1553
+ );
1554
+ server2.registerTool(
1555
+ "cowork_execute_skill",
1556
+ {
1557
+ title: "Execute Skill",
1558
+ description: "Run an AI skill on a file or input within a collaborative workspace. Available skills: explain, refactor, review, test, fix, document, search, commit.",
1559
+ inputSchema: {
1560
+ workspaceId: z18.string().min(1).describe("The workspace ID to execute the skill in"),
1561
+ skill: z18.enum(["explain", "refactor", "review", "test", "fix", "document", "search", "commit"]).describe("The skill to execute"),
1562
+ targetPath: z18.string().optional().describe("Optional file path to target with the skill"),
1563
+ input: z18.string().min(1).describe("Input or instructions for the skill execution")
1564
+ }
1565
+ },
1566
+ async ({ workspaceId, skill, targetPath, input }) => {
1567
+ try {
1568
+ const result = await client2.executeSkill(workspaceId, { skill, targetPath, input });
1569
+ return {
1570
+ content: [
1571
+ {
1572
+ type: "text",
1573
+ text: `Skill "${skill}" executed successfully.
1574
+ ${JSON.stringify(result.result, null, 2)}`
1575
+ }
1576
+ ]
1577
+ };
1578
+ } catch (err) {
1579
+ const message = err instanceof Error ? err.message : "Unknown error";
1580
+ return {
1581
+ content: [{ type: "text", text: `Error: ${message}` }],
1582
+ isError: true
1583
+ };
1584
+ }
1585
+ }
1586
+ );
1587
+ server2.registerTool(
1588
+ "cowork_apply_diff",
1589
+ {
1590
+ title: "Apply Operation Diff",
1591
+ description: "Apply a pending operation diff to a workspace file. Use after executing a skill that produces changes.",
1592
+ inputSchema: {
1593
+ workspaceId: z18.string().min(1).describe("The workspace ID containing the operation"),
1594
+ operationId: z18.string().min(1).describe("The operation ID whose diff should be applied")
1595
+ }
1596
+ },
1597
+ async ({ workspaceId, operationId }) => {
1598
+ try {
1599
+ const result = await client2.applyDiff(workspaceId, operationId);
1600
+ return {
1601
+ content: [
1602
+ {
1603
+ type: "text",
1604
+ text: result.applied ? `Diff applied successfully.
1605
+ ${JSON.stringify(result.result, null, 2)}` : "Diff could not be applied. The operation may have already been applied or expired."
1606
+ }
1607
+ ]
1608
+ };
1609
+ } catch (err) {
1610
+ const message = err instanceof Error ? err.message : "Unknown error";
1611
+ return {
1612
+ content: [{ type: "text", text: `Error: ${message}` }],
1613
+ isError: true
1614
+ };
1615
+ }
1616
+ }
1617
+ );
1618
+ }
1619
+
1620
+ // src/tools/video.ts
1621
+ import { z as z19 } from "zod";
1622
+ function registerVideoTools(server2, client2) {
1623
+ server2.registerTool(
1624
+ "video_generate",
1625
+ {
1626
+ title: "Generate Video",
1627
+ description: "Start an AI video generation job. Returns a job ID to track progress. Requires a paid plan with video credits.",
1628
+ inputSchema: {
1629
+ prompt: z19.string().min(1).max(2e3).describe("Text description of the video to generate"),
1630
+ model: z19.string().min(1).describe('Video generation model to use (e.g. "runway-gen3", "kling-v1", "pika-v2")'),
1631
+ durationSeconds: z19.number().int().min(1).max(60).describe("Duration of the video in seconds (1-60)"),
1632
+ aspectRatio: z19.enum(["16:9", "9:16", "1:1", "4:3", "3:4"]).optional().describe("Aspect ratio of the video (default: 16:9)"),
1633
+ style: z19.string().max(200).optional().describe('Optional style modifier (e.g. "cinematic", "anime", "documentary")')
1634
+ }
1635
+ },
1636
+ async ({ prompt, model, durationSeconds, aspectRatio, style }) => {
1637
+ try {
1638
+ const result = await client2.generateVideo({ prompt, model, durationSeconds, aspectRatio, style });
1639
+ return {
1640
+ content: [
1641
+ {
1642
+ type: "text",
1643
+ text: `Video generation started.
1644
+ ${JSON.stringify(result.job, null, 2)}`
1645
+ }
1646
+ ]
1647
+ };
1648
+ } catch (err) {
1649
+ const message = err instanceof Error ? err.message : "Unknown error";
1650
+ return {
1651
+ content: [{ type: "text", text: `Error: ${message}` }],
1652
+ isError: true
1653
+ };
1654
+ }
1655
+ }
1656
+ );
1657
+ server2.registerTool(
1658
+ "video_check_job",
1659
+ {
1660
+ title: "Check Video Job",
1661
+ description: "Check the status of a video generation job. Returns status, progress percentage, and result URL when complete.",
1662
+ inputSchema: {
1663
+ jobId: z19.string().min(1).describe("The video generation job ID to check")
1664
+ }
1665
+ },
1666
+ async ({ jobId }) => {
1667
+ try {
1668
+ const result = await client2.checkVideoJob(jobId);
1669
+ return {
1670
+ content: [
1671
+ {
1672
+ type: "text",
1673
+ text: JSON.stringify(result.job, null, 2)
1674
+ }
1675
+ ]
1676
+ };
1677
+ } catch (err) {
1678
+ const message = err instanceof Error ? err.message : "Unknown error";
1679
+ return {
1680
+ content: [{ type: "text", text: `Error: ${message}` }],
1681
+ isError: true
1682
+ };
1683
+ }
1684
+ }
1685
+ );
1686
+ server2.registerTool(
1687
+ "video_list_projects",
1688
+ {
1689
+ title: "List Video Projects",
1690
+ description: "List all video projects for the user. Projects organize multiple video scenes into a cohesive production.",
1691
+ inputSchema: {}
1692
+ },
1693
+ async () => {
1694
+ try {
1695
+ const result = await client2.listVideoProjects();
1696
+ if (result.projects.length === 0) {
1697
+ return {
1698
+ content: [{ type: "text", text: "No video projects found." }]
1699
+ };
1700
+ }
1701
+ return {
1702
+ content: [
1703
+ {
1704
+ type: "text",
1705
+ text: JSON.stringify(result, null, 2)
1706
+ }
1707
+ ]
1708
+ };
1709
+ } catch (err) {
1710
+ const message = err instanceof Error ? err.message : "Unknown error";
1711
+ return {
1712
+ content: [{ type: "text", text: `Error: ${message}` }],
1713
+ isError: true
1714
+ };
1715
+ }
1716
+ }
1717
+ );
1718
+ server2.registerTool(
1719
+ "video_create_project",
1720
+ {
1721
+ title: "Create Video Project",
1722
+ description: "Create a new video project. Projects let you plan scenes, generate clips, and stitch them into a final video.",
1723
+ inputSchema: {
1724
+ name: z19.string().min(1).max(200).describe("Name for the video project"),
1725
+ description: z19.string().max(1e3).optional().describe("Optional description of the video project")
1726
+ }
1727
+ },
1728
+ async ({ name, description }) => {
1729
+ try {
1730
+ const result = await client2.createVideoProject({ name, description });
1731
+ return {
1732
+ content: [
1733
+ {
1734
+ type: "text",
1735
+ text: `Video project created successfully.
1736
+ ${JSON.stringify(result.project, null, 2)}`
1737
+ }
1738
+ ]
1739
+ };
1740
+ } catch (err) {
1741
+ const message = err instanceof Error ? err.message : "Unknown error";
1742
+ return {
1743
+ content: [{ type: "text", text: `Error: ${message}` }],
1744
+ isError: true
1745
+ };
1746
+ }
1747
+ }
1748
+ );
1749
+ server2.registerTool(
1750
+ "video_plan_scenes",
1751
+ {
1752
+ title: "Plan Video Scenes",
1753
+ description: "Use AI to plan scenes for a video project. Breaks a description into individual scene prompts with timing and transitions.",
1754
+ inputSchema: {
1755
+ projectId: z19.string().min(1).describe("The video project ID to plan scenes for"),
1756
+ description: z19.string().min(1).max(5e3).describe("Description of the video to plan (story, concept, or script)"),
1757
+ sceneCount: z19.number().int().min(1).max(20).optional().describe("Number of scenes to plan (default: auto-determined by AI)")
1758
+ }
1759
+ },
1760
+ async ({ projectId, description, sceneCount }) => {
1761
+ try {
1762
+ const result = await client2.planVideoScenes(projectId, { description, sceneCount });
1763
+ return {
1764
+ content: [
1765
+ {
1766
+ type: "text",
1767
+ text: `Scene plan created (${result.scenes.length} scenes).
1768
+ ${JSON.stringify(result.scenes, null, 2)}`
1769
+ }
1770
+ ]
1771
+ };
1772
+ } catch (err) {
1773
+ const message = err instanceof Error ? err.message : "Unknown error";
1774
+ return {
1775
+ content: [{ type: "text", text: `Error: ${message}` }],
1776
+ isError: true
1777
+ };
1778
+ }
1779
+ }
1780
+ );
1781
+ server2.registerTool(
1782
+ "video_stitch",
1783
+ {
1784
+ title: "Stitch Video",
1785
+ description: "Stitch multiple generated video scenes into a single final video. All referenced jobs must be complete.",
1786
+ inputSchema: {
1787
+ projectId: z19.string().min(1).describe("The video project ID"),
1788
+ jobIds: z19.array(z19.string().min(1)).min(1).max(20).describe("Ordered list of completed video job IDs to stitch together")
1789
+ }
1790
+ },
1791
+ async ({ projectId, jobIds }) => {
1792
+ try {
1793
+ const result = await client2.stitchVideo(projectId, jobIds);
1794
+ return {
1795
+ content: [
1796
+ {
1797
+ type: "text",
1798
+ text: `Video stitch job started.
1799
+ ${JSON.stringify(result.job, null, 2)}`
1800
+ }
1801
+ ]
1802
+ };
1803
+ } catch (err) {
1804
+ const message = err instanceof Error ? err.message : "Unknown error";
1805
+ return {
1806
+ content: [{ type: "text", text: `Error: ${message}` }],
1807
+ isError: true
1808
+ };
1809
+ }
1810
+ }
1811
+ );
1812
+ }
1813
+
1235
1814
  // src/index.ts
1236
1815
  var config = loadConfig();
1237
1816
  var client = new AlmaClient(config);
1238
1817
  var server = new McpServer({
1239
1818
  name: "alma-mcp",
1240
- version: "1.3.0"
1819
+ version: "1.3.2"
1241
1820
  });
1242
1821
  registerAssembleTool(server, client);
1243
1822
  registerRememberTool(server, client);
@@ -1260,6 +1839,8 @@ registerListProceduresTool(server, client);
1260
1839
  registerCreateProcedureTool(server, client);
1261
1840
  registerPreviewContextTool(server, client);
1262
1841
  registerUpdateMemoryTool(server, client);
1842
+ registerCoworkTools(server, client);
1843
+ registerVideoTools(server, client);
1263
1844
  server.registerResource(
1264
1845
  "soul",
1265
1846
  "alma://soul",
@@ -1400,6 +1981,34 @@ server.registerResource(
1400
1981
  }
1401
1982
  }
1402
1983
  );
1984
+ server.registerResource(
1985
+ "video-budget",
1986
+ "alma://video-budget",
1987
+ {
1988
+ title: "Video Budget",
1989
+ description: "Current video generation credits, usage, and remaining quota",
1990
+ mimeType: "application/json"
1991
+ },
1992
+ async (uri) => {
1993
+ try {
1994
+ const result = await client.getVideoBudget();
1995
+ return {
1996
+ contents: [
1997
+ {
1998
+ uri: uri.href,
1999
+ mimeType: "application/json",
2000
+ text: JSON.stringify(result, null, 2)
2001
+ }
2002
+ ]
2003
+ };
2004
+ } catch (err) {
2005
+ const message = err instanceof Error ? err.message : "Unknown error";
2006
+ return {
2007
+ contents: [{ uri: uri.href, mimeType: "text/plain", text: `Error loading resource: ${message}` }]
2008
+ };
2009
+ }
2010
+ }
2011
+ );
1403
2012
  server.registerResource(
1404
2013
  "memories-by-category",
1405
2014
  new ResourceTemplate("alma://memories/{category}", {
@@ -1523,10 +2132,76 @@ server.registerResource(
1523
2132
  }
1524
2133
  }
1525
2134
  );
2135
+ server.prompt(
2136
+ "alma-context",
2137
+ "Assemble Alma's full context: soul identity, relevant memories, recent episodes, and procedures. Use this to give the AI persistent memory about the user.",
2138
+ {},
2139
+ async () => {
2140
+ try {
2141
+ const result = await client.assembleContext({ message: "general conversation" });
2142
+ return {
2143
+ messages: [
2144
+ {
2145
+ role: "user",
2146
+ content: {
2147
+ type: "text",
2148
+ text: `The following is your persistent memory and identity context from Alma. Use it to personalize your responses:
2149
+
2150
+ ${result.system_prompt}`
2151
+ }
2152
+ }
2153
+ ]
2154
+ };
2155
+ } catch (err) {
2156
+ const message = err instanceof Error ? err.message : "Unknown error";
2157
+ return {
2158
+ messages: [
2159
+ {
2160
+ role: "user",
2161
+ content: {
2162
+ type: "text",
2163
+ text: `[Alma context unavailable: ${message}]`
2164
+ }
2165
+ }
2166
+ ]
2167
+ };
2168
+ }
2169
+ }
2170
+ );
2171
+ server.prompt(
2172
+ "alma-recall",
2173
+ "Search Alma's memory for information about a specific topic.",
2174
+ { topic: { description: "The topic to recall memories about", required: true } },
2175
+ async (args) => {
2176
+ try {
2177
+ const result = await client.searchMemories({ q: args.topic ?? "", limit: 10 });
2178
+ const memoryText = result.memories.length > 0 ? result.memories.map((m) => `- ${m.content} (${m.category}, importance: ${m.importance})`).join("\n") : "No memories found for this topic.";
2179
+ return {
2180
+ messages: [
2181
+ {
2182
+ role: "user",
2183
+ content: {
2184
+ type: "text",
2185
+ text: `Here is what Alma remembers about "${args.topic}":
2186
+
2187
+ ${memoryText}`
2188
+ }
2189
+ }
2190
+ ]
2191
+ };
2192
+ } catch {
2193
+ return {
2194
+ messages: [
2195
+ { role: "user", content: { type: "text", text: `[Could not recall memories for "${args.topic}"]` } }
2196
+ ]
2197
+ };
2198
+ }
2199
+ }
2200
+ );
1526
2201
  async function main() {
1527
2202
  const transport = new StdioServerTransport();
1528
2203
  await server.connect(transport);
1529
- console.error("[alma-mcp] Server started on stdio (v1.3.0, 21 tools, 9 resources)");
2204
+ console.error("[alma-mcp] Server started on stdio (v1.3.2, 35 tools, 10 resources, 2 prompts)");
1530
2205
  if (process.env.ALMA_DEBUG) {
1531
2206
  console.error(`[alma-mcp] API: ${config.baseUrl}`);
1532
2207
  if (config.environmentId) {
package/package.json CHANGED
@@ -1,20 +1,30 @@
1
1
  {
2
2
  "name": "@olivaresai/alma-mcp",
3
- "version": "1.3.1",
4
- "description": "MCP Server for Alma — persistent memory for AI agents",
3
+ "version": "1.3.3",
4
+ "description": "MCP Server for Alma by OlivaresAI — persistent memory for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "alma-mcp": "./dist/index.js"
8
8
  },
9
9
  "main": "dist/index.js",
10
- "files": ["dist", "README.md", "LICENSE"],
11
- "scripts": {
12
- "build": "tsup",
13
- "dev": "tsx src/index.ts",
14
- "typecheck": "tsc --noEmit",
15
- "prepublishOnly": "npm run build"
16
- },
17
- "keywords": ["mcp", "alma", "ai", "memory", "persistent-memory", "ai-agent", "model-context-protocol", "claude", "cursor", "windsurf", "llm"],
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "mcp",
17
+ "alma",
18
+ "ai",
19
+ "memory",
20
+ "persistent-memory",
21
+ "ai-agent",
22
+ "model-context-protocol",
23
+ "claude",
24
+ "cursor",
25
+ "windsurf",
26
+ "llm"
27
+ ],
18
28
  "dependencies": {
19
29
  "@modelcontextprotocol/sdk": "^1.27.0",
20
30
  "zod": "^3.24.0"
@@ -35,5 +45,9 @@
35
45
  },
36
46
  "author": "OlivaresAI",
37
47
  "license": "SEE LICENSE IN LICENSE",
38
- "pnpm": {}
39
- }
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "dev": "tsx src/index.ts",
51
+ "typecheck": "tsc --noEmit"
52
+ }
53
+ }