prism-mcp-server 20.21.21 → 20.21.23

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.
@@ -61,20 +61,28 @@ export class AnthropicAdapter {
61
61
  debugLog("[AnthropicAdapter] Initialized");
62
62
  }
63
63
  // ─── Text Generation ─────────────────────────────────────────────────────
64
- async generateText(prompt, systemInstruction) {
65
- const model = getSettingSync("anthropic_model", getDefaultModel());
64
+ async generateText(prompt, systemInstruction, options) {
65
+ const model = options?.model || getSettingSync("anthropic_model", getDefaultModel());
66
66
  debugLog(`[AnthropicAdapter] generateText — model=${model}`);
67
67
  // Anthropic's Messages API uses system as a top-level field (not a message role).
68
68
  // This maps cleanly to LLMProvider's systemInstruction parameter.
69
69
  const response = await this.client.messages.create({
70
70
  model,
71
- max_tokens: getMaxTokens(),
71
+ max_tokens: options?.maxOutputTokens || getMaxTokens(),
72
72
  ...(systemInstruction ? { system: systemInstruction } : {}),
73
73
  messages: [{ role: "user", content: prompt }],
74
+ ...(options?.json ? {
75
+ tools: [{ name: "emit_json", description: "Return the requested JSON object.", input_schema: { type: "object", additionalProperties: true } }],
76
+ tool_choice: { type: "tool", name: "emit_json" },
77
+ } : {}),
74
78
  });
75
79
  // Extract text from the first ContentBlock.
76
80
  // Anthropic returns an array of content blocks; we only use text blocks.
77
81
  const block = response.content[0];
82
+ if (options?.json && block?.type === "tool_use")
83
+ return JSON.stringify(block.input);
84
+ if (options?.json)
85
+ throw new Error("[AnthropicAdapter] Structured response missing emit_json tool use");
78
86
  if (!block || block.type !== "text") {
79
87
  throw new Error(`[AnthropicAdapter] Unexpected response content type: ${block?.type ?? "empty"}`);
80
88
  }
@@ -62,14 +62,15 @@ export class GeminiAdapter {
62
62
  this.ai = new GoogleGenerativeAI(GOOGLE_API_KEY);
63
63
  }
64
64
  // ─── Text Generation ─────────────────────────────────────────────────────
65
- async generateText(prompt, systemInstruction) {
65
+ async generateText(prompt, systemInstruction, options) {
66
66
  // getGenerativeModel() is lightweight — it just binds model name + options.
67
67
  // The HTTP call happens inside generateContent() below.
68
68
  const model = this.ai.getGenerativeModel({
69
- model: TEXT_MODEL,
69
+ model: options?.model || TEXT_MODEL,
70
70
  // Only spread systemInstruction if provided — avoids sending an empty field
71
71
  // which could confuse some model versions.
72
72
  ...(systemInstruction ? { systemInstruction } : {}),
73
+ ...(options?.json || options?.maxOutputTokens ? { generationConfig: { ...(options?.json ? { responseMimeType: "application/json" } : {}), ...(options?.maxOutputTokens ? { maxOutputTokens: options.maxOutputTokens } : {}) } } : {}),
73
74
  });
74
75
  const result = await model.generateContent(prompt);
75
76
  // result.response.text() extracts the first candidate's text content.
@@ -84,10 +84,10 @@ export class OpenAIAdapter {
84
84
  debugLog(`[OpenAIAdapter] Initialized — baseURL=${baseURL}, keyless=${!apiKey}`);
85
85
  }
86
86
  // ─── Text Generation ─────────────────────────────────────────────────────
87
- async generateText(prompt, systemInstruction) {
87
+ async generateText(prompt, systemInstruction, options) {
88
88
  // Read model at call time (not constructor) so the user can hot-swap
89
89
  // the model setting without restarting the server.
90
- const model = getSettingSync("openai_model", "gpt-4o-mini");
90
+ const model = options?.model || getSettingSync("openai_model", "gpt-4o-mini");
91
91
  // Build message array: optional system message first, then user prompt.
92
92
  // This maps cleanly to Gemini's systemInstruction + user prompt pattern.
93
93
  const messages = [];
@@ -96,7 +96,7 @@ export class OpenAIAdapter {
96
96
  }
97
97
  messages.push({ role: "user", content: prompt });
98
98
  debugLog(`[OpenAIAdapter] generateText — model=${model}, messages=${messages.length}`);
99
- const response = await this.client.chat.completions.create({ model, messages });
99
+ const response = await this.client.chat.completions.create({ model, messages, ...(options?.json ? { response_format: { type: "json_object" } } : {}), ...(options?.maxOutputTokens ? { max_tokens: options.maxOutputTokens } : {}) });
100
100
  // choices[0] is always the primary completion. ?? "" returns empty string
101
101
  // on null content (e.g. if the model returned a function call instead).
102
102
  return response.choices[0]?.message?.content ?? "";
@@ -115,7 +115,7 @@ export class TracingLLMProvider {
115
115
  // will correctly evaluate to false.
116
116
  }
117
117
  // ── generateText ──────────────────────────────────────────────────────────
118
- async generateText(prompt, systemInstruction) {
118
+ async generateText(prompt, systemInstruction, options) {
119
119
  /**
120
120
  * Span: llm.generate_text
121
121
  *
@@ -130,7 +130,7 @@ export class TracingLLMProvider {
130
130
  });
131
131
  return context.with(trace.setSpan(context.active(), span), async () => {
132
132
  try {
133
- const result = await this.inner.generateText(prompt, systemInstruction);
133
+ const result = options ? await this.inner.generateText(prompt, systemInstruction, options) : await this.inner.generateText(prompt, systemInstruction);
134
134
  span.setStatus({ code: SpanStatusCode.OK });
135
135
  return result;
136
136
  }
@@ -54,7 +54,14 @@ function isPrivateIpv4(hostname) {
54
54
  return PRIVATE_IPV4_CIDRS.some(([start, end]) => value >= start && value <= end);
55
55
  }
56
56
  function isDisallowedIpv6(hostname) {
57
- const normalized = hostname.toLowerCase();
57
+ const normalized = hostname.replace(/^\[|\]$/g, "").toLowerCase();
58
+ // URL normalizes dotted IPv4-mapped addresses into two hexadecimal groups.
59
+ const mapped = normalized.match(/^::ffff:([a-f0-9]{1,4}):([a-f0-9]{1,4})$/);
60
+ if (mapped) {
61
+ const high = parseInt(mapped[1], 16);
62
+ const low = parseInt(mapped[2], 16);
63
+ return isPrivateIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
64
+ }
58
65
  return (normalized === "::1" ||
59
66
  normalized.startsWith("fc") || // fc00::/7
60
67
  normalized.startsWith("fd") || // fc00::/7
@@ -227,7 +234,9 @@ export class VerificationRunner {
227
234
  const parsed = JSON.parse(jsonContent);
228
235
  const suite = TestSuiteSchema.parse(parsed);
229
236
  if (harness) {
230
- VerificationRunner.verifyRubricHash(suite.tests, harness);
237
+ // The frozen hash covers the submitted JSON, including its key order.
238
+ // Schema parsing may reorder keys or supply defaults for execution.
239
+ VerificationRunner.verifyRubricHash(parsed.tests, harness);
231
240
  }
232
241
  const { preparedById, orderedIds, precomputed } = prepareAssertions(suite.tests, filterLayers, minSeverity, config);
233
242
  const outcomes = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.21.21",
3
+ "version": "20.21.23",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
5
  "description": "Persistent session memory for AI coding agents that never leaves your machine — including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
6
6
  "module": "index.ts",