@atrib/summarize 0.4.2 → 0.4.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.
- package/README.md +20 -9
- package/dist/index.js +1 -1
- package/dist/llm.js +49 -6
- package/dist/prompt.js +8 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -30,14 +30,22 @@ mcp__atrib-summarize__summarize({
|
|
|
30
30
|
|
|
31
31
|
OpenAI-compatible HTTP. Defaults to NVIDIA NIM with `qwen/qwen3.5-397b-a17b`. Override via env or per-call `model` input:
|
|
32
32
|
|
|
33
|
-
| Env var
|
|
34
|
-
|
|
35
|
-
| `ATRIB_SUMMARIZE_API_KEY`
|
|
36
|
-
| `ATRIB_SUMMARIZE_BASE_URL`
|
|
37
|
-
| `ATRIB_SUMMARIZE_MODEL`
|
|
38
|
-
| `ATRIB_SUMMARIZE_MAX_TOKENS`
|
|
39
|
-
| `ATRIB_SUMMARIZE_TEMPERATURE` | `0.3`
|
|
40
|
-
| `ATRIB_SUMMARIZE_TIMEOUT_MS`
|
|
33
|
+
| Env var | Default |
|
|
34
|
+
| ----------------------------- | ------------------------------------- |
|
|
35
|
+
| `ATRIB_SUMMARIZE_API_KEY` | fallback to provider env/cache |
|
|
36
|
+
| `ATRIB_SUMMARIZE_BASE_URL` | `https://integrate.api.nvidia.com/v1` |
|
|
37
|
+
| `ATRIB_SUMMARIZE_MODEL` | `qwen/qwen3.5-397b-a17b` |
|
|
38
|
+
| `ATRIB_SUMMARIZE_MAX_TOKENS` | `4000` |
|
|
39
|
+
| `ATRIB_SUMMARIZE_TEMPERATURE` | `0.3` |
|
|
40
|
+
| `ATRIB_SUMMARIZE_TIMEOUT_MS` | `120000` |
|
|
41
|
+
|
|
42
|
+
Provider env/cache fallback:
|
|
43
|
+
|
|
44
|
+
| Provider URL contains | Env var | Cache file |
|
|
45
|
+
| -------------------------- | -------------------- | ------------------------------------- |
|
|
46
|
+
| `integrate.api.nvidia.com` | `NVIDIA_API_KEY` | `~/.atrib/secrets/nvidia-api-key` |
|
|
47
|
+
| `api.cerebras.ai` | `CEREBRAS_API_KEY` | `~/.atrib/secrets/cerebras-api-key` |
|
|
48
|
+
| `cloudflare.com` | `CLOUDFLARE_API_KEY` | `~/.atrib/secrets/cloudflare-api-key` |
|
|
41
49
|
|
|
42
50
|
Without an API key, the tool returns a warnings-only response per the [§5.8](https://github.com/creatornader/atrib/blob/main/atrib-spec.md#58-degradation-contract) graceful-degradation contract.
|
|
43
51
|
|
|
@@ -66,12 +74,15 @@ Add to your MCP host config:
|
|
|
66
74
|
"command": "node",
|
|
67
75
|
"args": ["/path/to/atrib-summarize/dist/main.js"],
|
|
68
76
|
"env": {
|
|
69
|
-
"
|
|
77
|
+
"ATRIB_SUMMARIZE_MODEL": "qwen/qwen3.5-397b-a17b"
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
```
|
|
74
82
|
|
|
83
|
+
The API key can live in the host env or in the cache file above. Do not
|
|
84
|
+
write secret values into shared MCP config.
|
|
85
|
+
|
|
75
86
|
## Status
|
|
76
87
|
|
|
77
88
|
Initial scaffold (v0.1.0). 6 unit tests covering record selection (by hash, by context, unioned, missing-skip) + degradation paths (no inputs, no API key). Integration test against a real LLM is gated behind `ATRIB_SUMMARIZE_API_KEY` and not run in CI.
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ async function handleSummarize(input) {
|
|
|
75
75
|
}
|
|
76
76
|
const llmCfg = resolveLlmConfig(input.model);
|
|
77
77
|
if (!llmCfg) {
|
|
78
|
-
warnings.push('no LLM API key resolved (set ATRIB_SUMMARIZE_API_KEY or
|
|
78
|
+
warnings.push('no LLM API key resolved (set ATRIB_SUMMARIZE_API_KEY, NVIDIA_API_KEY, or ~/.atrib/secrets/nvidia-api-key); cannot synthesize');
|
|
79
79
|
return emptyOutput(warnings);
|
|
80
80
|
}
|
|
81
81
|
// Load local mirror once; filter to the requested set.
|
package/dist/llm.js
CHANGED
|
@@ -1,17 +1,59 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function readCacheSecret(cacheName) {
|
|
6
|
+
const home = process.env['HOME'] ?? homedir();
|
|
7
|
+
const path = join(home, '.atrib', 'secrets', cacheName);
|
|
8
|
+
if (!existsSync(path))
|
|
9
|
+
return '';
|
|
10
|
+
try {
|
|
11
|
+
return readFileSync(path, 'utf8').trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function firstSecret(...values) {
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
const trimmed = value?.trim();
|
|
20
|
+
if (trimmed)
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
function hostnameFor(baseUrl) {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(baseUrl).hostname.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function hostnameMatches(hostname, domain) {
|
|
34
|
+
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
35
|
+
}
|
|
36
|
+
function providerApiKey(baseUrl) {
|
|
37
|
+
const hostname = hostnameFor(baseUrl);
|
|
38
|
+
if (hostnameMatches(hostname, 'cerebras.ai')) {
|
|
39
|
+
return firstSecret(process.env['CEREBRAS_API_KEY'], readCacheSecret('cerebras-api-key'));
|
|
40
|
+
}
|
|
41
|
+
if (hostnameMatches(hostname, 'cloudflare.com')) {
|
|
42
|
+
return firstSecret(process.env['CLOUDFLARE_API_KEY'], readCacheSecret('cloudflare-api-key'));
|
|
43
|
+
}
|
|
44
|
+
return firstSecret(process.env['NVIDIA_API_KEY'], readCacheSecret('nvidia-api-key'), process.env['NVIDIA_NIM_API_KEY']);
|
|
45
|
+
}
|
|
2
46
|
/**
|
|
3
47
|
* Resolve LLM config from env with documented defaults. Caller-supplied
|
|
4
48
|
* model override (from the MCP tool input) wins over env.
|
|
5
49
|
*/
|
|
6
50
|
export function resolveLlmConfig(modelOverride) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
process.env['NVIDIA_NIM_API_KEY'] ??
|
|
10
|
-
'';
|
|
51
|
+
const baseUrl = process.env['ATRIB_SUMMARIZE_BASE_URL'] ?? 'https://integrate.api.nvidia.com/v1';
|
|
52
|
+
const apiKey = firstSecret(process.env['ATRIB_SUMMARIZE_API_KEY'], providerApiKey(baseUrl));
|
|
11
53
|
if (!apiKey)
|
|
12
54
|
return null;
|
|
13
55
|
return {
|
|
14
|
-
baseUrl
|
|
56
|
+
baseUrl,
|
|
15
57
|
model: modelOverride ?? process.env['ATRIB_SUMMARIZE_MODEL'] ?? 'qwen/qwen3.5-397b-a17b',
|
|
16
58
|
apiKey,
|
|
17
59
|
maxTokens: Number(process.env['ATRIB_SUMMARIZE_MAX_TOKENS'] ?? 4000),
|
|
@@ -50,7 +92,8 @@ export async function callLlm(cfg, systemMsg, userMsg) {
|
|
|
50
92
|
throw new Error(`LLM POST ${res.status}: ${text.slice(0, 500)}`);
|
|
51
93
|
}
|
|
52
94
|
const json = (await res.json());
|
|
53
|
-
const
|
|
95
|
+
const message = json.choices?.[0]?.message;
|
|
96
|
+
const content = message?.content || message?.reasoning_content || '';
|
|
54
97
|
if (!content)
|
|
55
98
|
throw new Error('LLM response had empty content');
|
|
56
99
|
return { content, model: json.model ?? cfg.model };
|
package/dist/prompt.js
CHANGED
|
@@ -64,6 +64,8 @@ function renderRecord(r) {
|
|
|
64
64
|
out.push(`tool: ${sc.toolName}`);
|
|
65
65
|
const c = sc.content;
|
|
66
66
|
if (c) {
|
|
67
|
+
if (typeof c['tool_name'] === 'string')
|
|
68
|
+
out.push(`tool: ${c['tool_name']}`);
|
|
67
69
|
if (typeof c['what'] === 'string')
|
|
68
70
|
out.push(`what: ${c['what']}`);
|
|
69
71
|
if (typeof c['why_noted'] === 'string')
|
|
@@ -78,15 +80,19 @@ function renderRecord(r) {
|
|
|
78
80
|
out.push(`new_position: ${c['new_position']}`);
|
|
79
81
|
if (typeof c['reason'] === 'string')
|
|
80
82
|
out.push(`reason: ${c['reason']}`);
|
|
83
|
+
if (c['args'])
|
|
84
|
+
out.push(`args (truncated): ${JSON.stringify(c['args']).slice(0, 1200)}`);
|
|
85
|
+
if (c['result'])
|
|
86
|
+
out.push(`result (truncated): ${JSON.stringify(c['result']).slice(0, 1200)}`);
|
|
81
87
|
if (Array.isArray(c['topics'])) {
|
|
82
88
|
out.push(`topics: ${c['topics'].filter((x) => typeof x === 'string').join(', ')}`);
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
// Tool-call sidecar may have args/result; keep brief to control prompt size.
|
|
86
92
|
if (sc.args)
|
|
87
|
-
out.push(`args (truncated): ${JSON.stringify(sc.args).slice(0,
|
|
93
|
+
out.push(`args (truncated): ${JSON.stringify(sc.args).slice(0, 1200)}`);
|
|
88
94
|
if (sc.result)
|
|
89
|
-
out.push(`result (truncated): ${JSON.stringify(sc.result).slice(0,
|
|
95
|
+
out.push(`result (truncated): ${JSON.stringify(sc.result).slice(0, 1200)}`);
|
|
90
96
|
}
|
|
91
97
|
else {
|
|
92
98
|
out.push('(no semantic sidecar available, record predates local-mirror sidecar pattern; only cryptographic metadata is present)');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atrib/summarize",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "MCP server for atrib. Synthesizes a narrative across N records via an OpenAI-compatible LLM so agents read context, not raw record bytes.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
13
13
|
"zod": "^3.25.76",
|
|
14
|
-
"@atrib/mcp": "0.
|
|
14
|
+
"@atrib/mcp": "0.12.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@types/node": "^25.
|
|
18
|
-
"tsx": "^4.22.
|
|
17
|
+
"@types/node": "^25.9.1",
|
|
18
|
+
"tsx": "^4.22.3",
|
|
19
19
|
"typescript": "^6.0.3",
|
|
20
|
-
"vitest": "^4.1.
|
|
20
|
+
"vitest": "^4.1.7"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist"
|