Skip to content

Commit 770161c

Browse files
chitcommitclaude
andauthored
fix: align ledger/evidence clients with actual service APIs (#62)
- ledgerClient: rewrite to use ChittyLedger entries/custody/verify API (was using phantom evidence/cases paths that don't exist) - evidenceClient: add CHITTYEVIDENCE_TOKEN auth header support - mcp.ts: ledger tools use addEntry/searchEntries/getChainOfCustody - Remove phantom ledger document fetch from timeline (covered by evidence) - Add CHITTYEVIDENCE_TOKEN to Env type - Add mcp-query.sh utility script Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6803c95 commit 770161c

5 files changed

Lines changed: 80 additions & 37 deletions

File tree

scripts/mcp-query.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/bash
2+
# Query ChittyCommand MCP endpoint with service token from KV
3+
set -euo pipefail
4+
5+
TOOL="${1:?Usage: mcp-query.sh <tool_name> [json_args]}"
6+
ARGS="${2:-\{\}}"
7+
8+
KV_ID=$(grep -A1 'COMMAND_KV' wrangler.toml | grep 'id' | head -1 | sed 's/.*= *"//;s/".*//')
9+
TOKEN=$(npx wrangler kv key get "mcp:service_token" --namespace-id="$KV_ID" --remote 2>/dev/null)
10+
11+
if [ -z "$TOKEN" ]; then
12+
echo "ERROR: Could not read mcp:service_token from KV" >&2
13+
exit 1
14+
fi
15+
16+
PAYLOAD=$(python3 -c "
17+
import json
18+
print(json.dumps({
19+
'jsonrpc': '2.0',
20+
'id': 1,
21+
'method': 'tools/call',
22+
'params': {'name': '$TOOL', 'arguments': json.loads('$ARGS')}
23+
}))
24+
")
25+
26+
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "https://command.chitty.cc/mcp" \
27+
-H "Content-Type: application/json" \
28+
-H "Authorization: Bearer $TOKEN" \
29+
-d "$PAYLOAD")
30+
31+
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
32+
BODY=$(echo "$RESPONSE" | head -n -1)
33+
34+
echo "HTTP: $HTTP_CODE"
35+
echo "$BODY" | python3 -m json.tool 2>/dev/null || echo "$BODY"

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export type Env = {
5454
CHITTYSCHEMA_URL?: string;
5555
CHITTYCERT_URL?: string;
5656
CHITTYEVIDENCE_URL?: string;
57+
CHITTYEVIDENCE_TOKEN?: string;
5758
CHITTY_CONNECT_TOKEN?: string;
5859
CHITTYLEDGER_TOKEN?: string;
5960
PLAID_CLIENT_ID?: string;

src/lib/integrations.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,36 +51,36 @@ export function ledgerClient(env: Env) {
5151
Object.entries(params).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)] as [string, string])
5252
).toString();
5353
const res = await fetch(`${baseUrl}/entries${qs ? `?${qs}` : ''}`, { headers });
54-
if (!res.ok) return [];
54+
if (!res.ok) { console.error(`[ledger] GET /entries failed: ${res.status}`); return []; }
5555
return await res.json() as Record<string, unknown>[];
56-
} catch { return []; }
56+
} catch (err) { console.error('[ledger] GET /entries error:', err); return []; }
5757
},
5858

5959
/** Get chain of custody for an entity */
6060
getChainOfCustody: async (entityId: string): Promise<Record<string, unknown>[]> => {
6161
try {
6262
const res = await fetch(`${baseUrl}/custody/${encodeURIComponent(entityId)}`, { headers });
63-
if (!res.ok) return [];
63+
if (!res.ok) { console.error(`[ledger] GET /custody failed: ${res.status}`); return []; }
6464
return await res.json() as Record<string, unknown>[];
65-
} catch { return []; }
65+
} catch (err) { console.error('[ledger] GET /custody error:', err); return []; }
6666
},
6767

6868
/** Verify ledger chain integrity */
6969
verifyChain: async (): Promise<{ valid: boolean; errors: string[] } | null> => {
7070
try {
7171
const res = await fetch(`${baseUrl}/verify`, { headers });
72-
if (!res.ok) return null;
72+
if (!res.ok) { console.error(`[ledger] GET /verify failed: ${res.status}`); return null; }
7373
return await res.json() as { valid: boolean; errors: string[] };
74-
} catch { return null; }
74+
} catch (err) { console.error('[ledger] GET /verify error:', err); return null; }
7575
},
7676

7777
/** Get ledger statistics */
7878
getStatistics: async (): Promise<Record<string, unknown> | null> => {
7979
try {
8080
const res = await fetch(`${baseUrl}/statistics`, { headers });
81-
if (!res.ok) return null;
81+
if (!res.ok) { console.error(`[ledger] GET /statistics failed: ${res.status}`); return null; }
8282
return await res.json() as Record<string, unknown>;
83-
} catch { return null; }
83+
} catch (err) { console.error('[ledger] GET /statistics error:', err); return null; }
8484
},
8585
};
8686
}
@@ -119,11 +119,14 @@ export function evidenceClient(env: Env) {
119119
if (!baseUrl) return null;
120120

121121
const headers: Record<string, string> = { 'X-Source-Service': 'chittycommand' };
122+
if (env.CHITTYEVIDENCE_TOKEN) {
123+
headers['Authorization'] = `Bearer ${env.CHITTYEVIDENCE_TOKEN}`;
124+
}
122125

123126
async function get<T>(path: string): Promise<T | null> {
124127
try {
125128
const res = await fetch(`${baseUrl}${path}`, { headers });
126-
if (!res.ok) return null;
129+
if (!res.ok) { console.error(`[evidence] GET ${path} failed: ${res.status}`); return null; }
127130
return await res.json() as T;
128131
} catch (err) {
129132
console.error(`[evidence] ${path} error:`, err);
@@ -458,20 +461,20 @@ export function connectClient(env: Env) {
458461
try {
459462
const obj = JSON.parse(cached) as { url?: string };
460463
if (obj?.url) return obj.url;
461-
} catch { /* ignore */ }
464+
} catch (err) { console.warn('[connect/discover] KV cache parse failed:', err); }
462465
}
463-
} catch { /* ignore */ }
466+
} catch (err) { console.warn('[connect/discover] KV read failed:', err); }
464467

465468
try {
466469
const res = await fetch(`${baseUrl}/api/discover/${encodeURIComponent(serviceName)}`, {
467470
headers: { 'X-Source-Service': 'chittycommand' },
468471
});
469-
if (!res.ok) return null;
472+
if (!res.ok) { console.error(`[connect/discover] ${serviceName} failed: ${res.status}`); return null; }
470473
const data = await res.json() as { url: string };
471474
// Store in KV with TTL
472-
try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch { /* ignore */ }
475+
try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch (err) { console.warn('[connect/discover] KV write failed:', err); }
473476
return data.url;
474-
} catch { return null; }
477+
} catch (err) { console.error('[connect/discover] fetch error:', err); return null; }
475478
},
476479

477480
// ── Prompt Registry (ContextConsciousness) ─────────────────

src/routes/mcp.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ async function executeTool(env: Env, sql: NeonQueryFunction<false, false>, toolN
621621
const caseId = String(args.case_id || '').trim();
622622
if (!caseId) throw new Error('Missing argument: case_id');
623623
const ev = evidenceClient(env);
624-
if (!ev) return { facts: [] };
624+
if (!ev) return { error: 'ChittyEvidence not configured', facts: [] };
625625
const facts = await ev.getStatementOfFacts(caseId);
626626
return { case_id: caseId, facts: facts || [] };
627627
}
@@ -630,7 +630,7 @@ async function executeTool(env: Env, sql: NeonQueryFunction<false, false>, toolN
630630
const caseId = String(args.case_id || '').trim();
631631
if (!caseId) throw new Error('Missing argument: case_id');
632632
const ev = evidenceClient(env);
633-
if (!ev) return { contradictions: [] };
633+
if (!ev) return { error: 'ChittyEvidence not configured', contradictions: [] };
634634
const contradictions = await ev.getContradictions(caseId);
635635
return { case_id: caseId, contradictions: contradictions || [] };
636636
}
@@ -798,17 +798,17 @@ async function executeTool(env: Env, sql: NeonQueryFunction<false, false>, toolN
798798
try {
799799
const cached = await env.COMMAND_KV.get(key);
800800
if (cached) {
801-
try { const obj = JSON.parse(cached) as { url?: string }; if (obj?.url) return { service, url: obj.url, cached: true }; } catch {}
801+
try { const obj = JSON.parse(cached) as { url?: string }; if (obj?.url) return { service, url: obj.url, cached: true }; } catch (err) { console.warn('[mcp/connect_discover] KV cache parse failed:', err); }
802802
}
803-
} catch {}
803+
} catch (err) { console.warn('[mcp/connect_discover] KV read failed:', err); }
804804
try {
805805
const res = await fetch(`${baseUrl}/api/discover/${encodeURIComponent(service)}`, {
806806
headers: { 'X-Source-Service': 'chittycommand' },
807807
signal: AbortSignal.timeout(3000),
808808
});
809809
if (!res.ok) return { error: `Service not found`, code: res.status };
810810
const data = await res.json() as { url: string };
811-
try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch {}
811+
try { await env.COMMAND_KV.put(key, JSON.stringify({ url: data.url }), { expirationTtl: 300 }); } catch (err) { console.warn('[mcp/connect_discover] KV write failed:', err); }
812812
return { service, url: data.url };
813813
} catch (err) {
814814
return { error: String(err) };
@@ -1228,25 +1228,29 @@ async function executeTool(env: Env, sql: NeonQueryFunction<false, false>, toolN
12281228
}
12291229

12301230
// Deadlines from DB (with date filtering)
1231-
let deadlines;
1232-
if (startDate && endDate) {
1233-
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`;
1234-
} else if (startDate) {
1235-
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} ORDER BY deadline_date ASC`;
1236-
} else if (endDate) {
1237-
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`;
1238-
} else {
1239-
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} ORDER BY deadline_date ASC`;
1240-
}
1241-
for (const d of deadlines) {
1242-
events.push({ id: `deadline:${d.id}`, date: d.deadline_date, type: 'deadline', title: d.title, deadlineType: d.deadline_type, status: d.status });
1243-
}
1231+
try {
1232+
let deadlines;
1233+
if (startDate && endDate) {
1234+
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`;
1235+
} else if (startDate) {
1236+
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date >= ${startDate} ORDER BY deadline_date ASC`;
1237+
} else if (endDate) {
1238+
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} AND deadline_date <= ${endDate} ORDER BY deadline_date ASC`;
1239+
} else {
1240+
deadlines = await sql`SELECT id, title, deadline_date, deadline_type, status FROM cc_legal_deadlines WHERE case_ref = ${caseId} ORDER BY deadline_date ASC`;
1241+
}
1242+
for (const d of deadlines) {
1243+
events.push({ id: `deadline:${d.id}`, date: d.deadline_date, type: 'deadline', title: d.title, deadlineType: d.deadline_type, status: d.status });
1244+
}
1245+
} catch (err) { console.error('[mcp/get_case_timeline] deadlines query error:', err); }
12441246

12451247
// Disputes from DB
1246-
const disputes = await sql`SELECT id, title, status, dispute_type, created_at FROM cc_disputes WHERE metadata->>'case_ref' = ${caseId} OR metadata->>'ledger_case_id' = ${caseId}`;
1247-
for (const d of disputes) {
1248-
events.push({ id: `dispute:${d.id}`, date: d.created_at, type: 'dispute', title: d.title, status: d.status, disputeType: d.dispute_type });
1249-
}
1248+
try {
1249+
const disputes = await sql`SELECT id, title, status, dispute_type, created_at FROM cc_disputes WHERE metadata->>'case_ref' = ${caseId} OR metadata->>'ledger_case_id' = ${caseId}`;
1250+
for (const d of disputes) {
1251+
events.push({ id: `dispute:${d.id}`, date: d.created_at, type: 'dispute', title: d.title, status: d.status, disputeType: d.dispute_type });
1252+
}
1253+
} catch (err) { console.error('[mcp/get_case_timeline] disputes query error:', err); }
12501254

12511255
// Documents already covered by ChittyEvidence facts above
12521256

tests/mcp.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ describe('MCP — tools/list', () => {
141141
expect(tools.length).toBeGreaterThanOrEqual(1);
142142
});
143143

144-
it('exposes exactly 38 tools', async () => {
144+
it('exposes exactly 48 tools', async () => {
145145
const { post } = buildApp();
146146
const res = await post({ jsonrpc: '2.0', id: 1, method: 'tools/list' });
147147
const json = await res.json() as Record<string, unknown>;

0 commit comments

Comments
 (0)