@cyanheads/calculator-mcp-server 0.4.0 → 0.4.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 (51) hide show
  1. package/AGENTS.md +20 -17
  2. package/CLAUDE.md +20 -17
  3. package/Dockerfile +14 -10
  4. package/LICENSE +1 -1
  5. package/README.md +42 -3
  6. package/changelog/0.1.x/0.1.0.md +20 -0
  7. package/changelog/0.1.x/0.1.1.md +28 -0
  8. package/changelog/0.1.x/0.1.10.md +25 -0
  9. package/changelog/0.1.x/0.1.11.md +29 -0
  10. package/changelog/0.1.x/0.1.12.md +21 -0
  11. package/changelog/0.1.x/0.1.13.md +20 -0
  12. package/changelog/0.1.x/0.1.14.md +18 -0
  13. package/changelog/0.1.x/0.1.15.md +20 -0
  14. package/changelog/0.1.x/0.1.16.md +26 -0
  15. package/changelog/0.1.x/0.1.17.md +24 -0
  16. package/changelog/0.1.x/0.1.18.md +21 -0
  17. package/changelog/0.1.x/0.1.19.md +18 -0
  18. package/changelog/0.1.x/0.1.2.md +22 -0
  19. package/changelog/0.1.x/0.1.20.md +21 -0
  20. package/changelog/0.1.x/0.1.21.md +26 -0
  21. package/changelog/0.1.x/0.1.22.md +33 -0
  22. package/changelog/0.1.x/0.1.23.md +32 -0
  23. package/changelog/0.1.x/0.1.24.md +16 -0
  24. package/changelog/0.1.x/0.1.25.md +23 -0
  25. package/changelog/0.1.x/0.1.26.md +16 -0
  26. package/changelog/0.1.x/0.1.3.md +19 -0
  27. package/changelog/0.1.x/0.1.4.md +20 -0
  28. package/changelog/0.1.x/0.1.5.md +19 -0
  29. package/changelog/0.1.x/0.1.6.md +26 -0
  30. package/changelog/0.1.x/0.1.7.md +19 -0
  31. package/changelog/0.1.x/0.1.8.md +18 -0
  32. package/changelog/0.1.x/0.1.9.md +29 -0
  33. package/changelog/0.2.x/0.2.0.md +18 -0
  34. package/changelog/0.3.x/0.3.0.md +19 -0
  35. package/changelog/0.3.x/0.3.1.md +30 -0
  36. package/changelog/0.3.x/0.3.2.md +18 -0
  37. package/changelog/0.3.x/0.3.3.md +27 -0
  38. package/changelog/0.3.x/0.3.4.md +17 -0
  39. package/changelog/0.3.x/0.3.5.md +23 -0
  40. package/changelog/0.4.x/0.4.0.md +28 -0
  41. package/changelog/0.4.x/0.4.1.md +28 -0
  42. package/changelog/template.md +168 -0
  43. package/dist/mcp-server/resources/definitions/help.resource.d.ts.map +1 -1
  44. package/dist/mcp-server/resources/definitions/help.resource.js +1 -0
  45. package/dist/mcp-server/resources/definitions/help.resource.js.map +1 -1
  46. package/dist/mcp-server/tools/definitions/calculate.tool.d.ts +3 -3
  47. package/dist/mcp-server/tools/definitions/calculate.tool.js.map +1 -1
  48. package/dist/services/math/math-service.d.ts.map +1 -1
  49. package/dist/services/math/math-service.js.map +1 -1
  50. package/package.json +23 -13
  51. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** calculator-mcp-server
4
- **Version:** 0.4.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.10`
4
+ **Version:** 0.4.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/sdk` 1.29.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` 2.0.0
8
8
  **Zod:** 4.4.3
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -50,7 +50,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
50
50
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
51
51
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
52
52
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
53
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
53
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
54
54
  - **Secrets in env vars only** — never hardcoded.
55
55
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
56
56
 
@@ -121,6 +121,9 @@ export const helpResource = resource('calculator://help', {
121
121
 
122
122
  ```ts
123
123
  // src/config/server-config.ts — lazy-parsed, separate from framework config
124
+ import { z } from '@cyanheads/mcp-ts-core';
125
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
126
+
124
127
  const ServerConfigSchema = z.object({
125
128
  maxExpressionLength: z.coerce.number().int().min(10).max(10_000).default(1000)
126
129
  .describe('Maximum allowed expression string length (10–10,000)'),
@@ -131,10 +134,10 @@ const ServerConfigSchema = z.object({
131
134
  });
132
135
  let _config: z.infer<typeof ServerConfigSchema> | undefined;
133
136
  export function getServerConfig() {
134
- _config ??= ServerConfigSchema.parse({
135
- maxExpressionLength: process.env.CALC_MAX_EXPRESSION_LENGTH,
136
- evaluationTimeoutMs: process.env.CALC_EVALUATION_TIMEOUT_MS,
137
- maxResultLength: process.env.CALC_MAX_RESULT_LENGTH,
137
+ _config ??= parseEnvConfig(ServerConfigSchema, {
138
+ maxExpressionLength: 'CALC_MAX_EXPRESSION_LENGTH',
139
+ evaluationTimeoutMs: 'CALC_EVALUATION_TIMEOUT_MS',
140
+ maxResultLength: 'CALC_MAX_RESULT_LENGTH',
138
141
  });
139
142
  return _config;
140
143
  }
@@ -154,13 +157,13 @@ Handlers receive a unified `ctx` object. Key properties:
154
157
 
155
158
  | Property | Description |
156
159
  |:---------|:------------|
157
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
158
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
159
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
160
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
160
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and `notifications/message` to the client, so treat it as client-visible. |
161
+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
162
+ | `ctx.requestInput` | Suspend and ask the caller for more input `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
163
+ | `ctx.inputs` | Reader over a retried request's responses `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |
164
+ | `ctx.enrich` | Success-path agent context — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches both response surfaces only when the definition declares an `enrichment` block. |
165
+ | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)`. Prepended to `content[]`; never enters `structuredContent`. |
161
166
  | `ctx.signal` | `AbortSignal` for cancellation. |
162
- | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
163
- | `ctx.enrich` | Success-path enrichment — `.notice(text)`, `.total(n)`, `.echo(query)`, `.delta({ field, before, after })`. Merges into `structuredContent` and `content[]` trailer automatically. |
164
167
  | `ctx.requestId` | Unique request ID. |
165
168
  | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
166
169
 
@@ -170,7 +173,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
173
 
171
174
  Handlers throw — the framework catches, classifies, and formats.
172
175
 
173
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire payload's `data.recovery.hint` (which the framework mirrors into `content[]` text), pass it explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
176
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words) and is the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire; override with an explicit recovery hint only when dynamic context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
174
177
 
175
178
  On the wire, tool errors mirror the success-path `format-parity` invariant — both `content[]` (markdown, read by clients like Claude Desktop) and `structuredContent.error` (JSON `{ code, message, data? }`, read by clients like Claude Code) carry the same payload, with `data.recovery.hint` mirrored into the markdown text when present.
176
179
 
@@ -184,7 +187,7 @@ errors: [
184
187
  ],
185
188
  async handler(input, ctx) {
186
189
  const item = await db.find(input.id);
187
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
190
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
188
191
  return item;
189
192
  }
190
193
  ```
@@ -280,7 +283,7 @@ Available skills:
280
283
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
281
284
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
282
285
  | `api-config` | AppConfig, parseConfig, env vars |
283
- | `api-context` | Context interface, logger, state, progress |
286
+ | `api-context` | Context interface, logger, state, input requests, enrichment |
284
287
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
285
288
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
286
289
  | `api-mirror` | MirrorService: persistent SQLite-backed local mirror of bulk upstream datasets — Tier 3 opt-in, Node/Bun only |
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** calculator-mcp-server
4
- **Version:** 0.4.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.10`
4
+ **Version:** 0.4.1
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/sdk` 1.29.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` 2.0.0
8
8
  **Zod:** 4.4.3
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -50,7 +50,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
50
50
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
51
51
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
52
52
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
53
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
53
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
54
54
  - **Secrets in env vars only** — never hardcoded.
55
55
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
56
56
 
@@ -121,6 +121,9 @@ export const helpResource = resource('calculator://help', {
121
121
 
122
122
  ```ts
123
123
  // src/config/server-config.ts — lazy-parsed, separate from framework config
124
+ import { z } from '@cyanheads/mcp-ts-core';
125
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
126
+
124
127
  const ServerConfigSchema = z.object({
125
128
  maxExpressionLength: z.coerce.number().int().min(10).max(10_000).default(1000)
126
129
  .describe('Maximum allowed expression string length (10–10,000)'),
@@ -131,10 +134,10 @@ const ServerConfigSchema = z.object({
131
134
  });
132
135
  let _config: z.infer<typeof ServerConfigSchema> | undefined;
133
136
  export function getServerConfig() {
134
- _config ??= ServerConfigSchema.parse({
135
- maxExpressionLength: process.env.CALC_MAX_EXPRESSION_LENGTH,
136
- evaluationTimeoutMs: process.env.CALC_EVALUATION_TIMEOUT_MS,
137
- maxResultLength: process.env.CALC_MAX_RESULT_LENGTH,
137
+ _config ??= parseEnvConfig(ServerConfigSchema, {
138
+ maxExpressionLength: 'CALC_MAX_EXPRESSION_LENGTH',
139
+ evaluationTimeoutMs: 'CALC_EVALUATION_TIMEOUT_MS',
140
+ maxResultLength: 'CALC_MAX_RESULT_LENGTH',
138
141
  });
139
142
  return _config;
140
143
  }
@@ -154,13 +157,13 @@ Handlers receive a unified `ctx` object. Key properties:
154
157
 
155
158
  | Property | Description |
156
159
  |:---------|:------------|
157
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
158
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
159
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
160
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
160
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and `notifications/message` to the client, so treat it as client-visible. |
161
+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
162
+ | `ctx.requestInput` | Suspend and ask the caller for more input `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
163
+ | `ctx.inputs` | Reader over a retried request's responses `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |
164
+ | `ctx.enrich` | Success-path agent context — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches both response surfaces only when the definition declares an `enrichment` block. |
165
+ | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)`. Prepended to `content[]`; never enters `structuredContent`. |
161
166
  | `ctx.signal` | `AbortSignal` for cancellation. |
162
- | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
163
- | `ctx.enrich` | Success-path enrichment — `.notice(text)`, `.total(n)`, `.echo(query)`, `.delta({ field, before, after })`. Merges into `structuredContent` and `content[]` trailer automatically. |
164
167
  | `ctx.requestId` | Unique request ID. |
165
168
  | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
166
169
 
@@ -170,7 +173,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
173
 
171
174
  Handlers throw — the framework catches, classifies, and formats.
172
175
 
173
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire payload's `data.recovery.hint` (which the framework mirrors into `content[]` text), pass it explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
176
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words) and is the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire; override with an explicit recovery hint only when dynamic context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
174
177
 
175
178
  On the wire, tool errors mirror the success-path `format-parity` invariant — both `content[]` (markdown, read by clients like Claude Desktop) and `structuredContent.error` (JSON `{ code, message, data? }`, read by clients like Claude Code) carry the same payload, with `data.recovery.hint` mirrored into the markdown text when present.
176
179
 
@@ -184,7 +187,7 @@ errors: [
184
187
  ],
185
188
  async handler(input, ctx) {
186
189
  const item = await db.find(input.id);
187
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
190
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
188
191
  return item;
189
192
  }
190
193
  ```
@@ -280,7 +283,7 @@ Available skills:
280
283
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
281
284
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
282
285
  | `api-config` | AppConfig, parseConfig, env vars |
283
- | `api-context` | Context interface, logger, state, progress |
286
+ | `api-context` | Context interface, logger, state, input requests, enrichment |
284
287
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
285
288
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
286
289
  | `api-mirror` | MirrorService: persistent SQLite-backed local mirror of bulk upstream datasets — Tier 3 opt-in, Node/Bun only |
package/Dockerfile CHANGED
@@ -4,15 +4,17 @@
4
4
  # This stage installs all dependencies (including dev), builds the TypeScript
5
5
  # source code into JavaScript, and prepares the production assets.
6
6
  # ==============================================================================
7
- FROM oven/bun:1.3 AS build
7
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS build
8
8
 
9
9
  WORKDIR /usr/src/app
10
10
 
11
11
  # Copy dependency manifests for optimized layer caching
12
12
  COPY package.json bun.lock ./
13
13
 
14
- # Install all dependencies (including dev dependencies for building)
15
- RUN bun install --frozen-lockfile
14
+ # Install all dependencies (including dev dependencies for building).
15
+ # The BuildKit cache mount persists Bun's global package cache across builds.
16
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
17
+ bun install --frozen-lockfile --ignore-scripts
16
18
 
17
19
  # Copy the rest of the source code
18
20
  COPY . .
@@ -28,7 +30,7 @@ RUN bun run build
28
30
  # application. It uses a slim base image and only includes production
29
31
  # dependencies and build artifacts.
30
32
  # ==============================================================================
31
- FROM oven/bun:1.3-slim AS production
33
+ FROM oven/bun:1.4.0-slim AS production
32
34
 
33
35
  WORKDIR /usr/src/app
34
36
 
@@ -39,7 +41,7 @@ ENV NODE_ENV=production
39
41
  # OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
40
42
  ARG APP_VERSION
41
43
  LABEL org.opencontainers.image.title="calculator-mcp-server"
42
- LABEL org.opencontainers.image.description="A calculator MCP server that lets any LLM verify mathematical computations. Evaluate, simplify, and differentiate expressions via a single tool. Powered by math.js v15."
44
+ LABEL org.opencontainers.image.description="Evaluate, simplify, and differentiate mathematical expressions via MCP. STDIO or Streamable HTTP."
43
45
  LABEL org.opencontainers.image.source="https://github.com/cyanheads/calculator-mcp-server"
44
46
  LABEL org.opencontainers.image.licenses="Apache-2.0"
45
47
  LABEL org.opencontainers.image.version="${APP_VERSION}"
@@ -47,16 +49,18 @@ LABEL org.opencontainers.image.version="${APP_VERSION}"
47
49
  # Copy dependency manifests
48
50
  COPY package.json bun.lock ./
49
51
 
50
- # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
51
- # that are not needed in the final production image.
52
- RUN bun install --production --frozen-lockfile --ignore-scripts
52
+ # Install only production dependencies, ignoring optional peer tiers and lifecycle
53
+ # scripts that are not needed in the final production image.
54
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
55
+ bun install --production --omit=peer --frozen-lockfile --ignore-scripts
53
56
 
54
57
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
55
58
  # These are not bundled by default to keep the base image lean. Enable at build time
56
59
  # with: docker build --build-arg OTEL_ENABLED=true
57
60
  ARG OTEL_ENABLED=true
58
- RUN if [ "$OTEL_ENABLED" = "true" ]; then \
59
- bun add @hono/otel \
61
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
62
+ if [ "$OTEL_ENABLED" = "true" ]; then \
63
+ bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
60
64
  @opentelemetry/instrumentation-http \
61
65
  @opentelemetry/exporter-metrics-otlp-http \
62
66
  @opentelemetry/exporter-trace-otlp-http \
package/LICENSE CHANGED
@@ -175,7 +175,7 @@
175
175
 
176
176
  END OF TERMS AND CONDITIONS
177
177
 
178
- Copyright 2026 cyanheads
178
+ Copyright 2026 Casey Hand @cyanheads
179
179
 
180
180
  Licensed under the Apache License, Version 2.0 (the "License");
181
181
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.4.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/calculator-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-%5E1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/calculator-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/calculator-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-%5E6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.4.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/calculator-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/calculator-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/calculator-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -69,7 +69,7 @@ Calculator-specific:
69
69
 
70
70
  - Hardened math.js v15 instance — dangerous functions disabled, evaluation sandboxed via `vm.runInNewContext()` with timeout
71
71
  - No auth required — all operations are read-only and stateless
72
- - Input validation: expression length limits, expression separator rejection (semicolons and newlines), variable name regex enforcement
72
+ - Input validation: expression length limits, expression separator rejection (semicolons and newlines), numeric-only scope values
73
73
  - Result validation: blocked result types (functions, parsers, result sets), configurable max result size
74
74
  - Scope sanitization: numeric-only values, prototype pollution prevention (blocked `__proto__`, `constructor`, etc.)
75
75
 
@@ -94,7 +94,7 @@ A public instance is available at `https://calculator.caseyjhand.com/mcp` — no
94
94
 
95
95
  ### Self-Hosted / Local
96
96
 
97
- Add to your MCP client config (e.g., `claude_desktop_config.json`):
97
+ Add one of the following to your MCP client configuration file:
98
98
 
99
99
  ```json
100
100
  {
@@ -108,6 +108,38 @@ Add to your MCP client config (e.g., `claude_desktop_config.json`):
108
108
  }
109
109
  ```
110
110
 
111
+ Or with npx (no Bun required):
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "calculator-mcp-server": {
117
+ "type": "stdio",
118
+ "command": "npx",
119
+ "args": ["-y", "@cyanheads/calculator-mcp-server@latest"]
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ Or with Docker:
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "calculator-mcp-server": {
131
+ "type": "stdio",
132
+ "command": "docker",
133
+ "args": [
134
+ "run", "-i", "--rm",
135
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
136
+ "ghcr.io/cyanheads/calculator-mcp-server:latest"
137
+ ]
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
111
143
  ### Prerequisites
112
144
 
113
145
  - [Bun v1.3.0](https://bun.sh/) or higher
@@ -139,10 +171,15 @@ bun install
139
171
  | `CALC_EVALUATION_TIMEOUT_MS` | Maximum evaluation time in milliseconds (100–30,000). | `5000` |
140
172
  | `CALC_MAX_RESULT_LENGTH` | Maximum result string length in characters (1,000–1,000,000). | `100000` |
141
173
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
174
+ | `MCP_HTTP_HOST` | Hostname for the HTTP server. | `localhost` |
142
175
  | `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
176
+ | `MCP_HTTP_ENDPOINT_PATH` | Path for the HTTP MCP endpoint. | `/mcp` |
177
+ | `MCP_HTTP_MAX_BODY_BYTES` | Maximum inbound HTTP request size; `0` disables the limit. | `1048576` |
143
178
  | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
144
179
  | `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
145
180
 
181
+ See [`.env.example`](./.env.example) for optional session, resumability, logging, and telemetry settings.
182
+
146
183
  ---
147
184
 
148
185
  ## Running the Server
@@ -168,6 +205,8 @@ docker build -t calculator-mcp-server .
168
205
  docker run -p 3010:3010 calculator-mcp-server
169
206
  ```
170
207
 
208
+ The image defaults to Streamable HTTP on port `3010`, stateless sessions, and logs at `/var/log/calculator-mcp-server`.
209
+
171
210
  ---
172
211
 
173
212
  ## Project Structure
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Initial project scaffold and design."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.0 — 2026-03-28
8
+
9
+ Initial project scaffold and design.
10
+
11
+ ## Added
12
+
13
+ - Project scaffold from `@cyanheads/mcp-ts-core` framework
14
+ - Design document (`docs/design.md`) covering tool surface, resource design, security model, service architecture, and implementation plan
15
+ - Agent protocol (`CLAUDE.md`) with server-specific conventions, MCP surface reference, security model, and configuration
16
+ - Server metadata (`server.json`) with stdio and streamable-http transport configurations
17
+ - Directory structure documentation (`docs/tree.md`)
18
+ - Server configuration schema with `CALC_MAX_EXPRESSION_LENGTH` and `CALC_EVALUATION_TIMEOUT_MS` env vars
19
+ - CI scaffolding: Dockerfile, `.env.example`, Biome config, Vitest config, devcheck config
20
+ - GitHub issue templates (bug report, feature request)
@@ -0,0 +1,28 @@
1
+ ---
2
+ summary: "Package metadata, documentation, and agent protocol polish."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.1 — 2026-03-28
8
+
9
+ Package metadata, documentation, and agent protocol polish.
10
+
11
+ ## Added
12
+
13
+ - README.md with tool/resource reference, configuration, getting started, and project structure
14
+ - LICENSE (Apache 2.0)
15
+ - `bunfig.toml` for Bun runtime configuration
16
+ - Server-specific env vars (`CALC_MAX_EXPRESSION_LENGTH`, `CALC_EVALUATION_TIMEOUT_MS`) in `server.json` package definitions
17
+ - `depcheck` devDependency
18
+
19
+ ## Changed
20
+
21
+ - Scoped package name to `@cyanheads/calculator-mcp-server`
22
+ - Updated `server.json` name to `io.github.cyanheads/calculator-mcp-server` with `bun` runtime hint
23
+ - Updated agent protocol (CLAUDE.md) with actual calculator tool/resource patterns; trimmed unused context properties
24
+ - Expanded package.json with keywords, homepage, bugs, author, and bun engine metadata
25
+ - Updated Dockerfile labels with description and source URL
26
+ - Updated `.env.example` with calculator-specific env vars
27
+ - Updated `devcheck.config.json` to ignore `depcheck` and `tsx` deps
28
+ - Regenerated `docs/tree.md`
@@ -0,0 +1,25 @@
1
+ ---
2
+ summary: "Framework bump to `@cyanheads/mcp-ts-core` 0.5.3, `parseEnvConfig` adoption, and skill sync."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.10 — 2026-04-20
8
+
9
+ Framework bump to `@cyanheads/mcp-ts-core` 0.5.3, `parseEnvConfig` adoption, and skill sync.
10
+
11
+ ## Added
12
+
13
+ - Adopted `parseEnvConfig` in `src/config/server-config.ts` — validation errors now name the actual env var (`CALC_MAX_EXPRESSION_LENGTH`) instead of the internal Zod path
14
+
15
+ ## Changed
16
+
17
+ - Upgraded `@cyanheads/mcp-ts-core` from `^0.3.5` to `^0.5.3` (spans 9 tags — new `parseEnvConfig` helper, format-parity linter rule with sentinel injection, devcheck CLAUDE.md/AGENTS.md sync check, Vitest 4 projects pattern, and multiple fixes)
18
+ - Upgraded `typescript` from `^6.0.2` to `^6.0.3`
19
+ - Synced seven external skills from the package (`add-tool`, `api-config`, `design-mcp-server`, `field-test`, `maintenance`, `polish-docs-meta`, `setup`) and mirrored all skills into `.claude/skills/`
20
+ - Regenerated `bun.lock` from a clean state
21
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.10`
22
+
23
+ ## Fixed
24
+
25
+ - Cleared the transitive hono moderate-severity advisory (GHSA-458j-xx4x-4375) via fresh lockfile resolution
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "Framework bump to `@cyanheads/mcp-ts-core` 0.7.0, handler simplification, and skill sync."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.11 — 2026-04-24
8
+
9
+ Framework bump to `@cyanheads/mcp-ts-core` 0.7.0, handler simplification, and skill sync.
10
+
11
+ ## Added
12
+
13
+ - Three new external skills from the framework: `api-linter` (rule reference), `release-and-publish` (post-wrapup publish workflow), and `security-pass` (8-axis MCP-specific audit)
14
+ - Three framework scripts newly synced into `scripts/`: `build-changelog.ts`, `check-docs-sync.ts`, `check-skills-sync.ts` (per the `maintenance` skill's new Phase C)
15
+
16
+ ## Changed
17
+
18
+ - Upgraded `@cyanheads/mcp-ts-core` from `^0.5.3` to `^0.7.0` (spans 18 tags — landing page at `/`, SEP-1649 Server Card, directory-based changelog system, recursive `describe-on-fields` linter, flattened ZodError messages with structured `data.issues`, `MCP_PUBLIC_URL` TLS-proxy override, per-request HTTP close race fix, `HtmlExtractor` utility, and the new skills above)
19
+ - Upgraded `@biomejs/biome` from `^2.4.12` to `^2.4.13` (patch) and `vitest` from `^4.1.4` to `^4.1.5` (patch)
20
+ - Resynced 15 external skills to match framework 0.7.0 versions (`add-tool` 1.6→1.8, `design-mcp-server` 2.4→2.7, `field-test` 1.2→2.0, `polish-docs-meta` 1.4→1.7, `setup` 1.3→1.5, `maintenance` 1.3→1.5, and nine others)
21
+ - Resynced `scripts/devcheck.ts` and `scripts/tree.ts` from the framework (devcheck now runs Docs Sync, Skills Sync, and Changelog Sync steps)
22
+ - Simplified the `calculate` tool handler — destructured `input` once, spread `MathResult` into the return, dropped per-case block scopes; ~40% fewer lines, same 27/27 tests passing
23
+ - Agent protocol (`CLAUDE.md` / `AGENTS.md`): added `security-pass` and `release-and-publish` to the What's Next? progression, added `api-linter` / `security-pass` / `release-and-publish` to the skills table, rewrote the Publishing section to point at the `release-and-publish` skill
24
+ - `.github/ISSUE_TEMPLATE/` descriptions now use the scoped package name (`@cyanheads/calculator-mcp-server`) for consistency
25
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.11`
26
+
27
+ ## Fixed
28
+
29
+ - Added `.describe()` to each variant of the `variable` and `precision` unions in the `calculate` input schema — the 0.6.16 framework bump extended `describe-on-fields` to recurse into union options, which flagged the previously-bare `z.literal('')` and sibling variants
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "Framework patch-series bump and a small `MathService` cleanup."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.12 — 2026-04-27
8
+
9
+ Framework patch-series bump and a small `MathService` cleanup.
10
+
11
+ ## Changed
12
+
13
+ - Upgraded `@cyanheads/mcp-ts-core` from `^0.7.0` to `^0.7.5` (spans five patch releases — HTTP Origin guard now fails closed for remote browser origins (loopback-only when `MCP_ALLOWED_ORIGINS` is unset), landing-page `requireAuth` validates bearer tokens, raw caller payloads removed from default logs, opt-in `LOG_LLM_INTERACTIONS`, `vitest.config` shipped as `.mjs` to avoid Node 22.7+ type-strip failure, new `Framework Antipatterns` devcheck step, `format-parity` numeric normalization tightened to reject lossy decimal-shift transforms, `describe-on-fields` linter exempts `z.literal` union variants, `landing.connectSnippets` operator override, and Cloudflare email-rewrite defense in connect snippets)
14
+ - Renamed `MathService.sanitizeScope` to `validateScope` — function only validated and threw, never sanitized; new name and `void` return reflect actual behavior
15
+ - Resynced 5 external skills from the framework (`maintenance` 1.5→1.6, `api-linter` 1.1→1.2, plus content-only updates to `api-utils`, `design-mcp-server`, `field-test`)
16
+ - Resynced `scripts/devcheck.ts` from the framework (adds the new `Framework Antipatterns` check step)
17
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.12`
18
+
19
+ ## Added
20
+
21
+ - `scripts/check-framework-antipatterns.ts` — pulled in alongside the updated `devcheck.ts` so the new check has a script to invoke. (Required because the `maintenance` skill's Phase C currently uses a hardcoded script list — see [cyanheads/mcp-ts-core#69](https://github.com/cyanheads/mcp-ts-core/issues/69).)
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Patch release: framework `0.7.5 → 0.7.6` bump, [#2](https://github.com/cyanheads/calculator-mcp-server/issues/2) fix lifting the single-expression contract into the `calculate` tool's published JSON Schema, and adoption of the framework template's new `start` script."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.13 — 2026-04-27
8
+
9
+ Patch release: framework `0.7.5 → 0.7.6` bump, [#2](https://github.com/cyanheads/calculator-mcp-server/issues/2) fix lifting the single-expression contract into the `calculate` tool's published JSON Schema, and adoption of the framework template's new `start` script.
10
+
11
+ ## Changed
12
+
13
+ - Tightened the `calculate` tool descriptions so the single-expression contract is visible at tool-discovery time, preempting LLM callers that batch with `;` or newlines (closes [#2](https://github.com/cyanheads/calculator-mcp-server/issues/2)). Tool-level description gains `One expression per call.`; the `expression` field describe leads with the constraint and disambiguates `;` semantics — `One mathematical expression per call — neither \`;\` nor newlines separate statements. Inside matrices, \`;\` separates rows (e.g. \`[1, 2; 3, 4]\`).` Existing runtime guard (`hasExpressionSeparator` in `math-service.ts`) unchanged — purely a discoverability improvement.
14
+ - Upgraded `@cyanheads/mcp-ts-core` from `^0.7.5` to `^0.7.6` (patch — `maintenance` skill Phase C now enumerates the installed `scripts/*.ts` directly instead of a hardcoded list, and `release-and-publish` / `setup` / `maintenance` skill prose was reworded so agents pick whichever git tooling is available rather than literal `git <cmd>` invocations)
15
+ - Resynced 3 external skills from the framework (`maintenance` 1.6→1.7, `release-and-publish` 2.1→2.2, `setup` 1.5→1.6)
16
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.13`
17
+
18
+ ## Added
19
+
20
+ - `start` script in `package.json` (`"start": "node dist/index.js"`) — adopted from the framework template's 0.7.6 update so external MCP runners that assume the npm-canonical `start` script work out of the box. The new script defers to `.env` for transport selection (no inline `MCP_TRANSPORT_TYPE` override); existing `start:stdio` / `start:http` variants unchanged.
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Patch release surfacing two field-test findings in the `calculate` tool: numeric results no longer flip into scientific notation at math.js's default `exp ≥ 5` threshold, and the schema descriptions stop naming a specific consumer."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.14 — 2026-04-27
8
+
9
+ Patch release surfacing two field-test findings in the `calculate` tool: numeric results no longer flip into scientific notation at math.js's default `exp ≥ 5` threshold, and the schema descriptions stop naming a specific consumer.
10
+
11
+ ## Fixed
12
+
13
+ - **`calculate` numeric formatting** — widen `math.format()` thresholds to `lowerExp: -6, upperExp: 21` (matching JS `Number.toString`) so normal-magnitude integers render as plain digits. `12345 * 6789` now returns `"83810205"` instead of `"8.3810205e+7"`; `factorial(10)` returns `"3628800"` instead of `"3.6288e+6"`. BigNumbers and very-tiny / very-large doubles still use exponential notation, and the `precision` parameter is unaffected. Implementation: `src/services/math/math-service.ts`.
14
+
15
+ ## Changed
16
+
17
+ - **`calculate` schema descriptions** — drop "for form-based clients" / "Blank values from form-based clients" phrasings from `variable.anyOf[0]`, `variable`, `precision.anyOf[0]`, and `precision` `.describe()` strings. The schema no longer references a specific consumer; the empty-string compat behavior is preserved. Implementation: `src/mcp-server/tools/definitions/calculate.tool.ts`.
18
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.14`.
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Patch release: framework `0.7.6 → 0.8.0` bump, agent protocol Errors section rewritten to lead with the new typed error contract pattern, and three external skills resynced from the framework."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.15 — 2026-04-28
8
+
9
+ Patch release: framework `0.7.6 → 0.8.0` bump, agent protocol Errors section rewritten to lead with the new typed error contract pattern, and three external skills resynced from the framework.
10
+
11
+ ## Changed
12
+
13
+ - Upgraded `@cyanheads/mcp-ts-core` from `^0.7.6` to `^0.8.0` (minor — adds typed error contracts via `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` with typed `ctx.fail(reason, …)`, advertises the failure surface in `tools/list` under `_meta['mcp-ts-core/errors']`; ships `httpErrorFromResponse` and `partialResult` utilities, three new error factories (`internalError`, `serializationError`, `databaseError`), and handler-body + error-contract conformance lint rules. Fully backwards compatible).
14
+ - Agent protocol files (`CLAUDE.md` / `AGENTS.md`) — Errors section rewritten to lead with the typed error contract as the recommended path, demote factories + auto-classification to the fallback. Added `add-app-tool` to the skills table. Added a form-client safety bullet to the checklist.
15
+ - Resynced 3 external skills from the framework (`maintenance` 1.7→1.8, `report-issue-framework` 1.3→1.4, `security-pass` 1.1→1.2 — `security-pass` Axis 7 leakage check now greps `ctx.fail(` and `httpErrorFromResponse(` alongside `new McpError`).
16
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.15`.
17
+
18
+ ## Added
19
+
20
+ - `scripts/split-changelog.ts` adopted from the framework template — supports the directory-based changelog convention used upstream. The calculator continues to use a flat `CHANGELOG.md`; the script is in place for a future migration.
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Adopt framework 0.8.0 typed error contract on `calculate`. Reclassify input-validation failures from `InvalidParams` (-32602) to `ValidationError` (-32007). 10 contract reasons declared. Wire-shape conformance test suite. Closes #3, #4."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.16 — 2026-04-28
8
+
9
+ Patch release: adopt the framework `0.8.0` typed error contract on the `calculate` tool and reclassify input-validation failures from JSON-RPC `InvalidParams` (`-32602`) to the framework's purpose-built `ValidationError` (`-32007`). Closes [#3](https://github.com/cyanheads/calculator-mcp-server/issues/3) and [#4](https://github.com/cyanheads/calculator-mcp-server/issues/4).
10
+
11
+ ## Changed
12
+
13
+ - **`calculate` error codes** — swapped 8 `invalidParams` (`-32602`) throws to `validationError` (`-32007`) across `src/services/math/math-service.ts` (7 sites) and `src/mcp-server/tools/definitions/calculate.tool.ts` (1 site). `serviceUnavailable` on evaluation timeout retained. Observability dashboards keyed on `mcp_error_classified_code` will now group these under `Client: Validation` instead of the unmapped JSON-RPC bucket. Closes [#3](https://github.com/cyanheads/calculator-mcp-server/issues/3).
14
+ - **`calculate` typed error contract** — declared all 10 failure modes as `errors: [{ reason, code, when, retryable? }]` on the tool definition; published in `tools/list` under `_meta['mcp-ts-core/errors']`. Reasons: `empty_expression`, `expression_too_long`, `multiple_expressions`, `reserved_scope_key`, `disallowed_result_type`, `result_too_large`, `undefined_result`, `parse_failed`, `derivative_missing_variable`, `evaluation_timeout` (`ServiceUnavailable`, `retryable: false`). The handler-side cross-field check now routes through `ctx.fail('derivative_missing_variable', …)`; service throws carry `data: { reason }` so the framework's auto-classifier preserves a stable identifier on the wire. Closes [#4](https://github.com/cyanheads/calculator-mcp-server/issues/4).
15
+
16
+ ## Added
17
+
18
+ - **Wire-shape conformance test suite** (`tests/mcp-server/tools/definitions/calculate.tool.test.ts`) — one test per contract reason (10 total) asserting the thrown `McpError`'s `code` and `data.reason` match the contract entry. Compensates for the framework lint's blind spot on service-thrown reasons. `result_too_large` and `evaluation_timeout` use a directly-constructed `MathService` with shrunken config (5-char limit / 1ms timeout) since the singleton's defaults can't trigger them.
19
+
20
+ ## Fixed
21
+
22
+ - **Test helper now wires `ctx.fail`** — existing `derivative` failure tests had been passing accidentally: `createMockContext()` doesn't attach `ctx.fail`, the resulting `TypeError` happened to serialize a substring `vitest.toThrow()` matched. New `mockCtx()` helper passes `errors: calculateTool.errors` so `ctx.fail` is properly available in tests.
23
+
24
+ ## Meta
25
+
26
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.16`.
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "Natural-language ergonomics: `calculate` now accepts `average`/`avg` aliases for `mean`, and `mph`, `knot`, `lightyear` units (with plurals and standard abbreviations). Closes #5, #6."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.17 — 2026-04-28
8
+
9
+ Patch release: natural-language ergonomics — the `calculate` tool now accepts `average`/`avg` as aliases for `mean`, and the `mph`, `knot`, and `lightyear` units (with their plurals and standard abbreviations). Closes [#5](https://github.com/cyanheads/calculator-mcp-server/issues/5) and [#6](https://github.com/cyanheads/calculator-mcp-server/issues/6).
10
+
11
+ ## Added
12
+
13
+ - **Function aliases** — `average` and `avg` registered as aliases for `mean` via `math.import()` in `MathService` constructor (`src/services/math/math-service.ts`). Both resolve to the same typed function as `mean`. Closes [#5](https://github.com/cyanheads/calculator-mcp-server/issues/5).
14
+ - **Custom units** — `mph` (= `1 mile/hour`), `knot` (= `1852 m/hour`, with aliases `knots`, `kt`, `kts`), and `lightyear` (= `9460730472580800 m`, with aliases `lightyears`, `ly`) registered via `math.createUnit()` in the constructor before the disabling step clobbers `createUnit` in the expression scope. Definitions use exact SI-derived values. Closes [#6](https://github.com/cyanheads/calculator-mcp-server/issues/6).
15
+ - **5 wire-shape tests** for the new aliases and units in `tests/mcp-server/tools/definitions/calculate.tool.test.ts` covering `average`/`avg` evaluation and `60 mph → m/s`, `1 lightyear → km`, `10 knots → m/s` conversions.
16
+
17
+ ## Changed
18
+
19
+ - **Help resource** (`calculator://help`) — Statistics line now reads `mean (aliases: average, avg)`; Common units line now includes `lightyear (ly)`, `mph`, `knot (kt)`. Discovery surface stays in sync with the registered surface.
20
+
21
+ ## Meta
22
+
23
+ - Bumped package, server metadata, README badge, and agent protocol files to `0.1.17`.
24
+ - [#7](https://github.com/cyanheads/calculator-mcp-server/issues/7) (intermediate overflow on factorial ratios) — left open with a [rejection comment](https://github.com/cyanheads/calculator-mcp-server/issues/7#issuecomment-4339684505) on the proposed BigNumber-as-default fix and a counter-proposal for an opt-in `numericType` tool parameter.