@getmarrow/mcp 1.1.1 → 2.0.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.
- package/README.md +141 -109
- package/dist/cli.d.ts +3 -2
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +214 -39
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +49 -51
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -220
- package/dist/index.js.map +1 -0
- package/package.json +11 -40
- package/src/cli.ts +235 -0
- package/src/index.ts +103 -0
- package/tsconfig.json +18 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Marrow MCP stdio server — collective memory for Claude and MCP agents.
|
|
5
|
+
* Exposes: marrow_think, marrow_commit, marrow_status
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as readline from 'readline';
|
|
9
|
+
import { marrowThink, marrowCommit, marrowStatus } from './index';
|
|
10
|
+
|
|
11
|
+
const API_KEY = process.env.MARROW_API_KEY || '';
|
|
12
|
+
const BASE_URL = process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai';
|
|
13
|
+
|
|
14
|
+
if (!API_KEY) {
|
|
15
|
+
process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// MCP protocol types
|
|
20
|
+
interface MCPRequest {
|
|
21
|
+
jsonrpc: '2.0';
|
|
22
|
+
id: string | number;
|
|
23
|
+
method: string;
|
|
24
|
+
params?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface MCPResponse {
|
|
28
|
+
jsonrpc: '2.0';
|
|
29
|
+
id: string | number;
|
|
30
|
+
result?: unknown;
|
|
31
|
+
error?: { code: number; message: string };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function send(response: MCPResponse): void {
|
|
35
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function success(id: string | number, result: unknown): void {
|
|
39
|
+
send({ jsonrpc: '2.0', id, result });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function error(id: string | number, code: number, message: string): void {
|
|
43
|
+
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Tool definitions
|
|
47
|
+
const TOOLS = [
|
|
48
|
+
{
|
|
49
|
+
name: 'marrow_think',
|
|
50
|
+
description:
|
|
51
|
+
'Get collective intelligence from the Marrow hive before acting. ' +
|
|
52
|
+
'Call this before every significant action. Pass previous_outcome ' +
|
|
53
|
+
'to auto-commit your last action and open a new memory session.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
action: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'What the agent is about to do',
|
|
60
|
+
},
|
|
61
|
+
type: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
enum: ['implementation', 'security', 'architecture', 'process', 'general'],
|
|
64
|
+
description: 'Type of action (default: general)',
|
|
65
|
+
},
|
|
66
|
+
context: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
description: 'Optional metadata about the current situation',
|
|
69
|
+
},
|
|
70
|
+
previous_decision_id: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'decision_id from previous think() call — auto-commits that session',
|
|
73
|
+
},
|
|
74
|
+
previous_success: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description: 'Did the previous action succeed?',
|
|
77
|
+
},
|
|
78
|
+
previous_outcome: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'What happened in the previous action (required if previous_decision_id provided)',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['action'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'marrow_commit',
|
|
88
|
+
description:
|
|
89
|
+
'Explicitly commit the result of an action to Marrow. ' +
|
|
90
|
+
'Optional — marrow_think() auto-commits if you pass previous_outcome. ' +
|
|
91
|
+
'Use when you need explicit control over commit timing.',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
decision_id: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'decision_id from the marrow_think call',
|
|
98
|
+
},
|
|
99
|
+
success: {
|
|
100
|
+
type: 'boolean',
|
|
101
|
+
description: 'Did the action succeed?',
|
|
102
|
+
},
|
|
103
|
+
outcome: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'What happened — be specific, this trains the hive',
|
|
106
|
+
},
|
|
107
|
+
caused_by: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Optional: what caused this action',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ['decision_id', 'success', 'outcome'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'marrow_status',
|
|
117
|
+
description: 'Check Marrow platform health and status.',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {},
|
|
121
|
+
required: [],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// Request handler
|
|
127
|
+
async function handleRequest(req: MCPRequest): Promise<void> {
|
|
128
|
+
const { id, method, params } = req;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
if (method === 'initialize') {
|
|
132
|
+
success(id, {
|
|
133
|
+
protocolVersion: '2024-11-05',
|
|
134
|
+
capabilities: { tools: {} },
|
|
135
|
+
serverInfo: { name: 'marrow', version: '2.0.0' },
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (method === 'tools/list') {
|
|
141
|
+
success(id, { tools: TOOLS });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (method === 'tools/call') {
|
|
146
|
+
const toolName = (params as Record<string, unknown>)?.name as string;
|
|
147
|
+
const args = ((params as Record<string, unknown>)?.arguments || {}) as Record<string, unknown>;
|
|
148
|
+
|
|
149
|
+
if (toolName === 'marrow_think') {
|
|
150
|
+
const result = await marrowThink(API_KEY, BASE_URL, {
|
|
151
|
+
action: args.action as string,
|
|
152
|
+
type: args.type as string | undefined,
|
|
153
|
+
context: args.context as Record<string, unknown> | undefined,
|
|
154
|
+
previous_decision_id: args.previous_decision_id as string | undefined,
|
|
155
|
+
previous_success: args.previous_success as boolean | undefined,
|
|
156
|
+
previous_outcome: args.previous_outcome as string | undefined,
|
|
157
|
+
});
|
|
158
|
+
success(id, {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: JSON.stringify(result, null, 2),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (toolName === 'marrow_commit') {
|
|
170
|
+
const result = await marrowCommit(API_KEY, BASE_URL, {
|
|
171
|
+
decision_id: args.decision_id as string,
|
|
172
|
+
success: args.success as boolean,
|
|
173
|
+
outcome: args.outcome as string,
|
|
174
|
+
caused_by: args.caused_by as string | undefined,
|
|
175
|
+
});
|
|
176
|
+
success(id, {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: JSON.stringify(result, null, 2),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (toolName === 'marrow_status') {
|
|
188
|
+
const result = await marrowStatus(API_KEY, BASE_URL);
|
|
189
|
+
success(id, {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: 'text',
|
|
193
|
+
text: JSON.stringify(result, null, 2),
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
error(id, -32601, `Unknown tool: ${toolName}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unknown method
|
|
205
|
+
error(id, -32601, `Method not found: ${method}`);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
+
error(id, -32603, `Internal error: ${message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// stdio server
|
|
213
|
+
const rl = readline.createInterface({
|
|
214
|
+
input: process.stdin,
|
|
215
|
+
output: process.stdout,
|
|
216
|
+
terminal: false,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
rl.on('line', (line) => {
|
|
220
|
+
const trimmed = line.trim();
|
|
221
|
+
if (!trimmed) return;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const req = JSON.parse(trimmed) as MCPRequest;
|
|
225
|
+
handleRequest(req).catch((err) => {
|
|
226
|
+
process.stderr.write(`Unhandled error: ${err}\n`);
|
|
227
|
+
});
|
|
228
|
+
} catch {
|
|
229
|
+
process.stderr.write(`Failed to parse request: ${trimmed}\n`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
rl.on('close', () => {
|
|
234
|
+
process.exit(0);
|
|
235
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface MarrowIntelligence {
|
|
2
|
+
similar: Array<{ outcome: string; confidence: number }>;
|
|
3
|
+
patterns: Array<{ pattern: string; frequency: number }>;
|
|
4
|
+
templates: Array<{ steps: unknown[]; success_rate: number }>;
|
|
5
|
+
shared: Array<{ outcome: string }>;
|
|
6
|
+
causal_chain: unknown | null;
|
|
7
|
+
success_rate: number;
|
|
8
|
+
priority_score: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ThinkResult {
|
|
12
|
+
decision_id: string;
|
|
13
|
+
intelligence: MarrowIntelligence;
|
|
14
|
+
stream_url: string;
|
|
15
|
+
previous_committed?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CommitResult {
|
|
19
|
+
committed: boolean;
|
|
20
|
+
success_rate: number;
|
|
21
|
+
insight: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StatusResult {
|
|
25
|
+
status: string;
|
|
26
|
+
version: string;
|
|
27
|
+
tiers: number;
|
|
28
|
+
uptime_ms: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function marrowThink(
|
|
32
|
+
apiKey: string,
|
|
33
|
+
baseUrl: string,
|
|
34
|
+
params: {
|
|
35
|
+
action: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
context?: Record<string, unknown>;
|
|
38
|
+
previous_decision_id?: string;
|
|
39
|
+
previous_success?: boolean;
|
|
40
|
+
previous_outcome?: string;
|
|
41
|
+
}
|
|
42
|
+
): Promise<ThinkResult> {
|
|
43
|
+
const body: Record<string, unknown> = {
|
|
44
|
+
action: params.action,
|
|
45
|
+
type: params.type || 'general',
|
|
46
|
+
};
|
|
47
|
+
if (params.context) body.context = params.context;
|
|
48
|
+
if (params.previous_decision_id) {
|
|
49
|
+
body.previous_decision_id = params.previous_decision_id;
|
|
50
|
+
body.previous_success = params.previous_success ?? true;
|
|
51
|
+
body.previous_outcome = params.previous_outcome ?? '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const res = await fetch(`${baseUrl}/v1/agent/think`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const json = await res.json() as { data: ThinkResult; error?: string };
|
|
64
|
+
if (json.error) throw new Error(json.error);
|
|
65
|
+
return json.data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function marrowCommit(
|
|
69
|
+
apiKey: string,
|
|
70
|
+
baseUrl: string,
|
|
71
|
+
params: {
|
|
72
|
+
decision_id: string;
|
|
73
|
+
success: boolean;
|
|
74
|
+
outcome: string;
|
|
75
|
+
caused_by?: string;
|
|
76
|
+
}
|
|
77
|
+
): Promise<CommitResult> {
|
|
78
|
+
const res = await fetch(`${baseUrl}/v1/agent/commit`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify(params),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const json = await res.json() as { data: CommitResult; error?: string };
|
|
88
|
+
if (json.error) throw new Error(json.error);
|
|
89
|
+
return json.data;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function marrowStatus(
|
|
93
|
+
apiKey: string,
|
|
94
|
+
baseUrl: string
|
|
95
|
+
): Promise<StatusResult> {
|
|
96
|
+
const res = await fetch(`${baseUrl}/v1/health`, {
|
|
97
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const json = await res.json() as { data: StatusResult; error?: string };
|
|
101
|
+
if (json.error) throw new Error(json.error);
|
|
102
|
+
return json.data;
|
|
103
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|