@recursiv/mcp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +135 -0
  2. package/dist/__tests__/scopes.test.d.ts +2 -0
  3. package/dist/__tests__/scopes.test.d.ts.map +1 -0
  4. package/dist/__tests__/scopes.test.js +85 -0
  5. package/dist/__tests__/scopes.test.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +125 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/approval-gate.d.ts +45 -0
  11. package/dist/lib/approval-gate.d.ts.map +1 -0
  12. package/dist/lib/approval-gate.js +143 -0
  13. package/dist/lib/approval-gate.js.map +1 -0
  14. package/dist/scopes.d.ts +99 -0
  15. package/dist/scopes.d.ts.map +1 -0
  16. package/dist/scopes.js +122 -0
  17. package/dist/scopes.js.map +1 -0
  18. package/dist/tools/__tests__/agents.test.d.ts +2 -0
  19. package/dist/tools/__tests__/agents.test.d.ts.map +1 -0
  20. package/dist/tools/__tests__/agents.test.js +248 -0
  21. package/dist/tools/__tests__/agents.test.js.map +1 -0
  22. package/dist/tools/__tests__/devtools.test.d.ts +2 -0
  23. package/dist/tools/__tests__/devtools.test.d.ts.map +1 -0
  24. package/dist/tools/__tests__/devtools.test.js +206 -0
  25. package/dist/tools/__tests__/devtools.test.js.map +1 -0
  26. package/dist/tools/__tests__/dispatcher.test.d.ts +2 -0
  27. package/dist/tools/__tests__/dispatcher.test.d.ts.map +1 -0
  28. package/dist/tools/__tests__/dispatcher.test.js +178 -0
  29. package/dist/tools/__tests__/dispatcher.test.js.map +1 -0
  30. package/dist/tools/__tests__/memory.test.d.ts +2 -0
  31. package/dist/tools/__tests__/memory.test.d.ts.map +1 -0
  32. package/dist/tools/__tests__/memory.test.js +151 -0
  33. package/dist/tools/__tests__/memory.test.js.map +1 -0
  34. package/dist/tools/__tests__/projects.test.d.ts +2 -0
  35. package/dist/tools/__tests__/projects.test.d.ts.map +1 -0
  36. package/dist/tools/__tests__/projects.test.js +168 -0
  37. package/dist/tools/__tests__/projects.test.js.map +1 -0
  38. package/dist/tools/__tests__/sandbox.test.d.ts +2 -0
  39. package/dist/tools/__tests__/sandbox.test.d.ts.map +1 -0
  40. package/dist/tools/__tests__/sandbox.test.js +113 -0
  41. package/dist/tools/__tests__/sandbox.test.js.map +1 -0
  42. package/dist/tools/__tests__/social.test.d.ts +2 -0
  43. package/dist/tools/__tests__/social.test.d.ts.map +1 -0
  44. package/dist/tools/__tests__/social.test.js +127 -0
  45. package/dist/tools/__tests__/social.test.js.map +1 -0
  46. package/dist/tools/__tests__/swarms.test.d.ts +2 -0
  47. package/dist/tools/__tests__/swarms.test.d.ts.map +1 -0
  48. package/dist/tools/__tests__/swarms.test.js +320 -0
  49. package/dist/tools/__tests__/swarms.test.js.map +1 -0
  50. package/dist/tools/__tests__/templates.test.d.ts +2 -0
  51. package/dist/tools/__tests__/templates.test.d.ts.map +1 -0
  52. package/dist/tools/__tests__/templates.test.js +176 -0
  53. package/dist/tools/__tests__/templates.test.js.map +1 -0
  54. package/dist/tools/agents.d.ts +4 -0
  55. package/dist/tools/agents.d.ts.map +1 -0
  56. package/dist/tools/agents.js +170 -0
  57. package/dist/tools/agents.js.map +1 -0
  58. package/dist/tools/devtools.d.ts +4 -0
  59. package/dist/tools/devtools.d.ts.map +1 -0
  60. package/dist/tools/devtools.js +511 -0
  61. package/dist/tools/devtools.js.map +1 -0
  62. package/dist/tools/dispatcher.d.ts +4 -0
  63. package/dist/tools/dispatcher.d.ts.map +1 -0
  64. package/dist/tools/dispatcher.js +647 -0
  65. package/dist/tools/dispatcher.js.map +1 -0
  66. package/dist/tools/memory.d.ts +4 -0
  67. package/dist/tools/memory.d.ts.map +1 -0
  68. package/dist/tools/memory.js +92 -0
  69. package/dist/tools/memory.js.map +1 -0
  70. package/dist/tools/platform.d.ts +4 -0
  71. package/dist/tools/platform.d.ts.map +1 -0
  72. package/dist/tools/platform.js +359 -0
  73. package/dist/tools/platform.js.map +1 -0
  74. package/dist/tools/projects.d.ts +4 -0
  75. package/dist/tools/projects.d.ts.map +1 -0
  76. package/dist/tools/projects.js +79 -0
  77. package/dist/tools/projects.js.map +1 -0
  78. package/dist/tools/rate-limiter.d.ts +53 -0
  79. package/dist/tools/rate-limiter.d.ts.map +1 -0
  80. package/dist/tools/rate-limiter.js +87 -0
  81. package/dist/tools/rate-limiter.js.map +1 -0
  82. package/dist/tools/remaining.d.ts +8 -0
  83. package/dist/tools/remaining.d.ts.map +1 -0
  84. package/dist/tools/remaining.js +283 -0
  85. package/dist/tools/remaining.js.map +1 -0
  86. package/dist/tools/sandbox.d.ts +4 -0
  87. package/dist/tools/sandbox.d.ts.map +1 -0
  88. package/dist/tools/sandbox.js +35 -0
  89. package/dist/tools/sandbox.js.map +1 -0
  90. package/dist/tools/social.d.ts +11 -0
  91. package/dist/tools/social.d.ts.map +1 -0
  92. package/dist/tools/social.js +136 -0
  93. package/dist/tools/social.js.map +1 -0
  94. package/dist/tools/swarms.d.ts +4 -0
  95. package/dist/tools/swarms.d.ts.map +1 -0
  96. package/dist/tools/swarms.js +184 -0
  97. package/dist/tools/swarms.js.map +1 -0
  98. package/dist/tools/templates.d.ts +4 -0
  99. package/dist/tools/templates.d.ts.map +1 -0
  100. package/dist/tools/templates.js +102 -0
  101. package/dist/tools/templates.js.map +1 -0
  102. package/dist/tools/utils.d.ts +9 -0
  103. package/dist/tools/utils.d.ts.map +1 -0
  104. package/dist/tools/utils.js +8 -0
  105. package/dist/tools/utils.js.map +1 -0
  106. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @recursiv/mcp
2
+
3
+ MCP (Model Context Protocol) server for [Recursiv](https://recursiv.io) — give AI assistants access to your projects, agents, and social infrastructure.
4
+
5
+ Exposes **24 tools** across three categories:
6
+
7
+ | Category | Tools | Examples |
8
+ |----------|-------|---------|
9
+ | **Social** | 7 | List/create posts, search, communities, chat |
10
+ | **Projects** | 10 | Create/deploy projects, sandboxes, code execution |
11
+ | **Agents** | 7 | Create/manage AI agents, chat, conversations |
12
+
13
+ ## Setup
14
+
15
+ ### 1. Get an API key
16
+
17
+ Create an API key at [recursiv.io/settings/api-keys](https://recursiv.io/settings/api-keys).
18
+
19
+ ### 2. Configure your AI assistant
20
+
21
+ #### Claude Desktop
22
+
23
+ Add to your `claude_desktop_config.json`:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "recursiv": {
29
+ "command": "npx",
30
+ "args": ["-y", "@recursiv/mcp"],
31
+ "env": {
32
+ "RECURSIV_API_KEY": "your-api-key-here"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ Config file location:
40
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
41
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
42
+
43
+ #### Claude Code
44
+
45
+ Add to your project's `.mcp.json`:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "recursiv": {
51
+ "command": "npx",
52
+ "args": ["-y", "@recursiv/mcp"],
53
+ "env": {
54
+ "RECURSIV_API_KEY": "your-api-key-here"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Or run directly:
62
+
63
+ ```bash
64
+ RECURSIV_API_KEY=your-key npx @recursiv/mcp
65
+ ```
66
+
67
+ ### 3. Environment variables
68
+
69
+ | Variable | Required | Description |
70
+ |----------|----------|-------------|
71
+ | `RECURSIV_API_KEY` | Yes | Your Recursiv API key |
72
+ | `RECURSIV_BASE_URL` | No | API base URL (default: `https://recursiv.io/api/v1`) |
73
+
74
+ `SOCIAL_DEV_API_KEY` is also accepted as a fallback for backwards compatibility.
75
+
76
+ ## Available Tools
77
+
78
+ ### Social
79
+
80
+ | Tool | Description |
81
+ |------|-------------|
82
+ | `list_posts` | List posts with optional filtering by community or author |
83
+ | `get_post` | Get a specific post by ID with its replies |
84
+ | `create_post` | Create a new post (plain text or markdown) |
85
+ | `search_posts` | Search posts by text query |
86
+ | `list_communities` | List public communities |
87
+ | `send_message` | Send a message in a conversation |
88
+ | `list_conversations` | List all conversations for the authenticated user |
89
+
90
+ ### Projects
91
+
92
+ | Tool | Description |
93
+ |------|-------------|
94
+ | `list_projects` | List projects accessible to the authenticated user |
95
+ | `get_project` | Get detailed information about a specific project |
96
+ | `create_project` | Create a new project in an organization |
97
+ | `delete_project` | Delete a project permanently |
98
+ | `deploy_project` | Deploy a project to production or create a preview deployment |
99
+ | `execute_code` | Execute code in a project sandbox |
100
+ | `start_sandbox` | Start an E2B sandbox for a project |
101
+ | `stop_sandbox` | Stop and clean up a project sandbox |
102
+ | `get_deployment` | Get the status of a specific deployment |
103
+ | `get_deployment_logs` | Get build/runtime logs for a deployment |
104
+
105
+ ### Agents
106
+
107
+ | Tool | Description |
108
+ |------|-------------|
109
+ | `list_agents` | List all AI agents owned by the authenticated user |
110
+ | `get_agent` | Get detailed information about a specific AI agent |
111
+ | `create_agent` | Create a new AI agent with custom model and system prompt |
112
+ | `update_agent` | Update an existing AI agent configuration |
113
+ | `delete_agent` | Delete an AI agent permanently |
114
+ | `chat_with_agent` | Send a message to an AI agent and get a response |
115
+ | `list_agent_conversations` | List an agent's conversations |
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ # Install dependencies
121
+ pnpm install
122
+
123
+ # Run in development mode
124
+ RECURSIV_API_KEY=your-key pnpm --filter @recursiv/mcp dev
125
+
126
+ # Type check
127
+ pnpm --filter @recursiv/mcp typecheck
128
+
129
+ # Build
130
+ pnpm --filter @recursiv/mcp build
131
+ ```
132
+
133
+ ## License
134
+
135
+ FSL-1.1-ALv2
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scopes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scopes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,85 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ import { ScopedToolRegistry, resolveGrantedScopes, Scopes } from '../scopes.js';
5
+ describe('ScopedToolRegistry', () => {
6
+ let server;
7
+ let toolSpy;
8
+ beforeEach(() => {
9
+ server = new McpServer({ name: 'test', version: '0.0.1' });
10
+ toolSpy = vi.fn();
11
+ server.tool = toolSpy;
12
+ });
13
+ it('registers all tools when grantedScopes is null (no env var)', () => {
14
+ const registry = new ScopedToolRegistry(server, null);
15
+ registry.tool('tool_a', 'A', [Scopes.POSTS_READ], { id: z.string() }, vi.fn());
16
+ registry.tool('tool_b', 'B', [Scopes.AGENTS_WRITE], { id: z.string() }, vi.fn());
17
+ expect(toolSpy).toHaveBeenCalledTimes(2);
18
+ expect(registry.registered).toBe(2);
19
+ expect(registry.skipped).toBe(0);
20
+ });
21
+ it('registers tools with empty requiredScopes regardless of granted scopes', () => {
22
+ const registry = new ScopedToolRegistry(server, new Set(['posts:read']));
23
+ registry.tool('tool_a', 'A', [], { id: z.string() }, vi.fn());
24
+ expect(toolSpy).toHaveBeenCalledTimes(1);
25
+ expect(registry.registered).toBe(1);
26
+ expect(registry.skipped).toBe(0);
27
+ });
28
+ it('registers tools when all required scopes are granted', () => {
29
+ const granted = new Set(['posts:read', 'posts:write', 'agents:read']);
30
+ const registry = new ScopedToolRegistry(server, granted);
31
+ registry.tool('tool_a', 'A', [Scopes.POSTS_READ], { id: z.string() }, vi.fn());
32
+ registry.tool('tool_b', 'B', [Scopes.POSTS_READ, Scopes.POSTS_WRITE], { id: z.string() }, vi.fn());
33
+ expect(toolSpy).toHaveBeenCalledTimes(2);
34
+ expect(registry.registered).toBe(2);
35
+ expect(registry.skipped).toBe(0);
36
+ });
37
+ it('skips tools when required scopes are missing', () => {
38
+ const granted = new Set(['posts:read']);
39
+ const registry = new ScopedToolRegistry(server, granted);
40
+ registry.tool('tool_a', 'A', [Scopes.POSTS_READ], { id: z.string() }, vi.fn());
41
+ registry.tool('tool_b', 'B', [Scopes.AGENTS_WRITE], { id: z.string() }, vi.fn());
42
+ registry.tool('tool_c', 'C', [Scopes.POSTS_READ, Scopes.AGENTS_WRITE], { id: z.string() }, vi.fn());
43
+ expect(toolSpy).toHaveBeenCalledTimes(1);
44
+ expect(registry.registered).toBe(1);
45
+ expect(registry.skipped).toBe(2);
46
+ });
47
+ it('skips all tools when granted scopes are empty', () => {
48
+ const registry = new ScopedToolRegistry(server, new Set());
49
+ registry.tool('tool_a', 'A', [Scopes.POSTS_READ], { id: z.string() }, vi.fn());
50
+ registry.tool('tool_b', 'B', [], { id: z.string() }, vi.fn()); // empty = always registered
51
+ expect(toolSpy).toHaveBeenCalledTimes(1); // only tool_b
52
+ expect(registry.registered).toBe(1);
53
+ expect(registry.skipped).toBe(1);
54
+ });
55
+ });
56
+ describe('resolveGrantedScopes', () => {
57
+ const originalEnv = process.env.RECURSIV_API_KEY_SCOPES;
58
+ afterEach(() => {
59
+ if (originalEnv === undefined) {
60
+ delete process.env.RECURSIV_API_KEY_SCOPES;
61
+ }
62
+ else {
63
+ process.env.RECURSIV_API_KEY_SCOPES = originalEnv;
64
+ }
65
+ });
66
+ it('returns null when env var is not set', () => {
67
+ delete process.env.RECURSIV_API_KEY_SCOPES;
68
+ expect(resolveGrantedScopes()).toBeNull();
69
+ });
70
+ it('returns null when env var is empty', () => {
71
+ process.env.RECURSIV_API_KEY_SCOPES = '';
72
+ expect(resolveGrantedScopes()).toBeNull();
73
+ });
74
+ it('parses comma-separated scopes', () => {
75
+ process.env.RECURSIV_API_KEY_SCOPES = 'posts:read,agents:write,memory:read';
76
+ const result = resolveGrantedScopes();
77
+ expect(result).toEqual(new Set(['posts:read', 'agents:write', 'memory:read']));
78
+ });
79
+ it('trims whitespace from scope values', () => {
80
+ process.env.RECURSIV_API_KEY_SCOPES = ' posts:read , agents:write ';
81
+ const result = resolveGrantedScopes();
82
+ expect(result).toEqual(new Set(['posts:read', 'agents:write']));
83
+ });
84
+ });
85
+ //# sourceMappingURL=scopes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.test.js","sourceRoot":"","sources":["../../src/__tests__/scopes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import { config } from 'dotenv';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ // Load .env from project root (two levels up from packages/mcp/dist/)
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ config({ path: resolve(__dirname, '..', '..', '..', '.env') });
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { Recursiv } from '@recursiv/sdk';
11
+ import { ScopedToolRegistry, resolveGrantedScopes } from './scopes.js';
12
+ import { registerSocialTools } from './tools/social.js';
13
+ import { registerProjectTools } from './tools/projects.js';
14
+ import { registerAgentTools } from './tools/agents.js';
15
+ import { registerSandboxTools } from './tools/sandbox.js';
16
+ import { registerMemoryTools } from './tools/memory.js';
17
+ import { registerDispatcherTools } from './tools/dispatcher.js';
18
+ import { registerSwarmTools } from './tools/swarms.js';
19
+ import { registerTemplateTools } from './tools/templates.js';
20
+ import { registerRemainingTools } from './tools/remaining.js';
21
+ import { registerDevTools } from './tools/devtools.js';
22
+ import { registerPlatformTools } from './tools/platform.js';
23
+ function registerServerResources(server) {
24
+ const setupGuide = [
25
+ '# Recursiv MCP setup',
26
+ '',
27
+ 'This MCP server is intended to be launched over stdio by a local MCP client.',
28
+ '',
29
+ 'Codex repo-local config:',
30
+ '[mcp_servers.recursiv]',
31
+ 'command = "node"',
32
+ 'args = ["packages/mcp/dist/index.js"]',
33
+ 'env = { "RECURSIV_API_KEY" = "YOUR_RECURSIV_API_KEY" }',
34
+ '',
35
+ 'Notes:',
36
+ '- Codex reads ~/.codex/config.toml or .codex/config.toml.',
37
+ '- .codex/config.toml and opencode.json should stay ignored because they contain secrets.',
38
+ '- This server exposes tools and a small read-only resource surface for MCP verification.',
39
+ ].join('\n');
40
+ const manifest = {
41
+ name: 'recursiv',
42
+ version: '0.1.0',
43
+ transport: 'stdio',
44
+ baseUrl,
45
+ authEnvVars: ['RECURSIV_API_KEY', 'SOCIAL_DEV_API_KEY'],
46
+ toolGroups: [
47
+ 'social',
48
+ 'projects',
49
+ 'agents',
50
+ 'sandbox',
51
+ 'memory',
52
+ 'dispatcher',
53
+ 'swarms',
54
+ 'templates',
55
+ ],
56
+ };
57
+ server.registerResource('server_manifest', 'recursiv://server/manifest', {
58
+ title: 'Recursiv MCP manifest',
59
+ description: 'Machine-readable metadata about the Recursiv MCP server.',
60
+ mimeType: 'application/json',
61
+ }, async (uri) => ({
62
+ contents: [
63
+ {
64
+ uri: uri.href,
65
+ mimeType: 'application/json',
66
+ text: JSON.stringify(manifest, null, 2),
67
+ },
68
+ ],
69
+ }));
70
+ server.registerResource('setup_guide', 'recursiv://server/setup', {
71
+ title: 'Recursiv MCP setup guide',
72
+ description: 'Human-readable setup notes for Codex and other local MCP clients.',
73
+ mimeType: 'text/markdown',
74
+ }, async (uri) => ({
75
+ contents: [
76
+ {
77
+ uri: uri.href,
78
+ mimeType: 'text/markdown',
79
+ text: setupGuide,
80
+ },
81
+ ],
82
+ }));
83
+ }
84
+ const apiKey = process.env.RECURSIV_API_KEY || process.env.SOCIAL_DEV_API_KEY;
85
+ if (!apiKey) {
86
+ console.error('Missing RECURSIV_API_KEY environment variable. Set it to your Recursiv API key.');
87
+ process.exit(1);
88
+ }
89
+ const baseUrl = process.env.RECURSIV_BASE_URL || 'https://api.recursiv.io/api/v1';
90
+ const client = new Recursiv({ apiKey, baseUrl });
91
+ // Anonymous client for try_code tool — no API key needed
92
+ const anonClient = new Recursiv({ anonymous: true, baseUrl });
93
+ const server = new McpServer({
94
+ name: 'recursiv',
95
+ version: '0.1.0',
96
+ });
97
+ // Resolve granted scopes from RECURSIV_API_KEY_SCOPES env var.
98
+ // If unset, all tools are registered (backwards-compatible).
99
+ const grantedScopes = resolveGrantedScopes();
100
+ const registry = new ScopedToolRegistry(server, grantedScopes);
101
+ registerSocialTools(registry, client);
102
+ registerProjectTools(registry, client);
103
+ registerAgentTools(registry, client);
104
+ registerSandboxTools(registry, anonClient);
105
+ registerMemoryTools(registry, client);
106
+ registerDispatcherTools(registry, client);
107
+ registerSwarmTools(registry, client);
108
+ registerTemplateTools(registry, client);
109
+ registerRemainingTools(registry, client);
110
+ registerDevTools(registry, client);
111
+ registerPlatformTools(registry, client);
112
+ if (grantedScopes !== null) {
113
+ const scopeList = [...grantedScopes].join(', ');
114
+ console.error(`[recursiv-mcp] Scope filter active — registered ${registry.registered} tools, skipped ${registry.skipped} (scopes: ${scopeList})`);
115
+ }
116
+ registerServerResources(server);
117
+ async function main() {
118
+ const transport = new StdioServerTransport();
119
+ await server.connect(transport);
120
+ }
121
+ main().catch((err) => {
122
+ console.error('MCP server error:', err);
123
+ process.exit(1);
124
+ });
125
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * RED Approval Gate — lightweight client-side approval check for
3
+ * social write operations.
4
+ *
5
+ * Social write tools (create_post, send_message, generate_image, etc.)
6
+ * are RED-classified: they require explicit admin approval before
7
+ * execution. This module calls the server's /approvals endpoints via
8
+ * raw fetch (with the same API key used by the SDK) so it stays
9
+ * decoupled from the SDK's internal HttpClient.
10
+ *
11
+ * The gate is **fail-closed**: if the approval service is unreachable
12
+ * or returns an unexpected error, the tool call is blocked.
13
+ */
14
+ export type ApprovalStatus = 'pending' | 'approved' | 'denied';
15
+ export interface ApprovalCheckResult {
16
+ id: string;
17
+ status: ApprovalStatus;
18
+ denial_reason?: string | null;
19
+ }
20
+ export interface ApprovalGateConfig {
21
+ baseUrl: string;
22
+ apiKey: string;
23
+ orgId: string;
24
+ }
25
+ /**
26
+ * Check or create an approval request for a RED-classified tool call.
27
+ *
28
+ * 1. Compute a deterministic hash of (toolName, args).
29
+ * 2. GET /approvals/check to look up an existing approval.
30
+ * 3. If none exists (404), POST /approvals to create a pending request.
31
+ * 4. Return { allowed: true } only if status === 'approved'.
32
+ *
33
+ * On any network / server error the gate is fail-closed.
34
+ */
35
+ export declare function requireApproval(toolName: string, args: Record<string, unknown>, config: ApprovalGateConfig | null): Promise<{
36
+ allowed: true;
37
+ } | {
38
+ allowed: false;
39
+ message: string;
40
+ }>;
41
+ /**
42
+ * Fetch the current status of a specific approval request.
43
+ */
44
+ export declare function checkApprovalStatus(approvalId: string, config: Pick<ApprovalGateConfig, 'baseUrl' | 'apiKey'> | null): Promise<ApprovalCheckResult>;
45
+ //# sourceMappingURL=approval-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-gate.d.ts","sourceRoot":"","sources":["../../src/lib/approval-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,cAAc,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAqBD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAChC,OAAO,CAAC;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuFlE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,QAAQ,CAAC,GAAG,IAAI,GAC5D,OAAO,CAAC,mBAAmB,CAAC,CAkB9B"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * RED Approval Gate — lightweight client-side approval check for
3
+ * social write operations.
4
+ *
5
+ * Social write tools (create_post, send_message, generate_image, etc.)
6
+ * are RED-classified: they require explicit admin approval before
7
+ * execution. This module calls the server's /approvals endpoints via
8
+ * raw fetch (with the same API key used by the SDK) so it stays
9
+ * decoupled from the SDK's internal HttpClient.
10
+ *
11
+ * The gate is **fail-closed**: if the approval service is unreachable
12
+ * or returns an unexpected error, the tool call is blocked.
13
+ */
14
+ import { createHash } from 'node:crypto';
15
+ /** Hash args deterministically for idempotent approval lookups. */
16
+ function hashArgs(args) {
17
+ const sorted = JSON.stringify(args, Object.keys(args).sort());
18
+ return createHash('sha256').update(sorted).digest('hex');
19
+ }
20
+ /** Truncate large arg values so the approval context stays readable. */
21
+ function truncateArgs(args) {
22
+ const result = {};
23
+ for (const [key, value] of Object.entries(args)) {
24
+ if (typeof value === 'string' && value.length > 200) {
25
+ result[key] = value.slice(0, 200) + '...';
26
+ }
27
+ else {
28
+ result[key] = value;
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ /**
34
+ * Check or create an approval request for a RED-classified tool call.
35
+ *
36
+ * 1. Compute a deterministic hash of (toolName, args).
37
+ * 2. GET /approvals/check to look up an existing approval.
38
+ * 3. If none exists (404), POST /approvals to create a pending request.
39
+ * 4. Return { allowed: true } only if status === 'approved'.
40
+ *
41
+ * On any network / server error the gate is fail-closed.
42
+ */
43
+ export async function requireApproval(toolName, args, config) {
44
+ // If no approval config, skip the gate (no org ID configured).
45
+ if (!config)
46
+ return { allowed: true };
47
+ const { baseUrl, apiKey, orgId } = config;
48
+ const argsHash = hashArgs(args);
49
+ const headers = {
50
+ Authorization: `Bearer ${apiKey}`,
51
+ 'Content-Type': 'application/json',
52
+ Accept: 'application/json',
53
+ };
54
+ try {
55
+ // Step 1: Check for an existing approval
56
+ const checkUrl = new URL(`${baseUrl}/approvals/check`);
57
+ checkUrl.searchParams.set('tool', toolName);
58
+ checkUrl.searchParams.set('args_hash', argsHash);
59
+ checkUrl.searchParams.set('org_id', orgId);
60
+ const checkResp = await fetch(checkUrl.toString(), { method: 'GET', headers });
61
+ if (checkResp.ok) {
62
+ const { data } = (await checkResp.json());
63
+ if (data.status === 'approved') {
64
+ return { allowed: true };
65
+ }
66
+ if (data.status === 'denied') {
67
+ return {
68
+ allowed: false,
69
+ message: `Action denied by admin. Request ID: ${data.id}. Reason: ${data.denial_reason ?? 'No reason provided.'}`,
70
+ };
71
+ }
72
+ // Still pending
73
+ return {
74
+ allowed: false,
75
+ message: `Approval required. Request ID: ${data.id}. An admin must approve this action before it can be executed. Check status later with check_approval_status.`,
76
+ };
77
+ }
78
+ // 404 means no existing approval — fall through to create one
79
+ if (checkResp.status !== 404) {
80
+ const errText = await checkResp.text().catch(() => checkResp.statusText);
81
+ return {
82
+ allowed: false,
83
+ message: `Social write blocked (fail-closed). Approval check returned HTTP ${checkResp.status}: ${errText}`,
84
+ };
85
+ }
86
+ // Step 2: Create a new pending approval request
87
+ const createResp = await fetch(`${baseUrl}/approvals`, {
88
+ method: 'POST',
89
+ headers,
90
+ body: JSON.stringify({
91
+ tool_name: toolName,
92
+ args_hash: argsHash,
93
+ org_id: orgId,
94
+ requester_context: {
95
+ tool: toolName,
96
+ args_preview: truncateArgs(args),
97
+ },
98
+ }),
99
+ });
100
+ if (!createResp.ok) {
101
+ const errText = await createResp.text().catch(() => createResp.statusText);
102
+ return {
103
+ allowed: false,
104
+ message: `Social write blocked (fail-closed). Could not create approval request — HTTP ${createResp.status}: ${errText}`,
105
+ };
106
+ }
107
+ const { data: created } = (await createResp.json());
108
+ return {
109
+ allowed: false,
110
+ message: `Approval required. Request ID: ${created.id}. An admin must approve this action before it can be executed.`,
111
+ };
112
+ }
113
+ catch (err) {
114
+ // Fail-closed: block on any unexpected error
115
+ const detail = err instanceof Error ? err.message : String(err);
116
+ return {
117
+ allowed: false,
118
+ message: `Social write blocked (fail-closed). The approval service returned an error: ${detail}. Contact an admin.`,
119
+ };
120
+ }
121
+ }
122
+ /**
123
+ * Fetch the current status of a specific approval request.
124
+ */
125
+ export async function checkApprovalStatus(approvalId, config) {
126
+ if (!config)
127
+ throw new Error('Approval gate not configured (RECURSIV_ORG_ID unset)');
128
+ const { baseUrl, apiKey } = config;
129
+ const resp = await fetch(`${baseUrl}/approvals/${encodeURIComponent(approvalId)}`, {
130
+ method: 'GET',
131
+ headers: {
132
+ Authorization: `Bearer ${apiKey}`,
133
+ Accept: 'application/json',
134
+ },
135
+ });
136
+ if (!resp.ok) {
137
+ const errText = await resp.text().catch(() => resp.statusText);
138
+ throw new Error(`Approval status check failed — HTTP ${resp.status}: ${errText}`);
139
+ }
140
+ const { data } = (await resp.json());
141
+ return data;
142
+ }
143
+ //# sourceMappingURL=approval-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-gate.js","sourceRoot":"","sources":["../../src/lib/approval-gate.ts"],"names":[],"mappings":"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"}