calc-mcp-server 0.1.0
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/.claude/commands/opsx/apply.md +152 -0
- package/.claude/commands/opsx/archive.md +157 -0
- package/.claude/commands/opsx/bulk-archive.md +242 -0
- package/.claude/commands/opsx/continue.md +114 -0
- package/.claude/commands/opsx/explore.md +174 -0
- package/.claude/commands/opsx/ff.md +94 -0
- package/.claude/commands/opsx/new.md +69 -0
- package/.claude/commands/opsx/onboard.md +534 -0
- package/.claude/commands/opsx/sync.md +134 -0
- package/.claude/commands/opsx/verify.md +164 -0
- package/.claude/settings.local.json +8 -0
- package/.claude/skills/npm-publish/SKILL.md +164 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +161 -0
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
- package/.claude/skills/openspec-explore/SKILL.md +289 -0
- package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
- package/.claude/skills/openspec-new-change/SKILL.md +74 -0
- package/.claude/skills/openspec-onboard/SKILL.md +538 -0
- package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
- package/CLAUDE.md +92 -0
- package/README.md +319 -0
- package/build/engines/decimal.d.ts +10 -0
- package/build/engines/decimal.d.ts.map +1 -0
- package/build/engines/decimal.js +61 -0
- package/build/engines/decimal.js.map +1 -0
- package/build/engines/programmer.d.ts +18 -0
- package/build/engines/programmer.d.ts.map +1 -0
- package/build/engines/programmer.js +103 -0
- package/build/engines/programmer.js.map +1 -0
- package/build/errors/handler.d.ts +10 -0
- package/build/errors/handler.d.ts.map +1 -0
- package/build/errors/handler.js +37 -0
- package/build/errors/handler.js.map +1 -0
- package/build/errors/types.d.ts +25 -0
- package/build/errors/types.d.ts.map +1 -0
- package/build/errors/types.js +2 -0
- package/build/errors/types.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +16 -0
- package/build/index.js.map +1 -0
- package/build/mcp/server.d.ts +3 -0
- package/build/mcp/server.d.ts.map +1 -0
- package/build/mcp/server.js +270 -0
- package/build/mcp/server.js.map +1 -0
- package/build/mcp/tools/ascii.d.ts +11 -0
- package/build/mcp/tools/ascii.d.ts.map +1 -0
- package/build/mcp/tools/ascii.js +93 -0
- package/build/mcp/tools/ascii.js.map +1 -0
- package/build/mcp/tools/basic.d.ts +6 -0
- package/build/mcp/tools/basic.d.ts.map +1 -0
- package/build/mcp/tools/basic.js +34 -0
- package/build/mcp/tools/basic.js.map +1 -0
- package/build/mcp/tools/conversion.d.ts +8 -0
- package/build/mcp/tools/conversion.d.ts.map +1 -0
- package/build/mcp/tools/conversion.js +81 -0
- package/build/mcp/tools/conversion.js.map +1 -0
- package/build/mcp/tools/programmer.d.ts +6 -0
- package/build/mcp/tools/programmer.d.ts.map +1 -0
- package/build/mcp/tools/programmer.js +29 -0
- package/build/mcp/tools/programmer.js.map +1 -0
- package/build/parser/ast.d.ts +47 -0
- package/build/parser/ast.d.ts.map +1 -0
- package/build/parser/ast.js +2 -0
- package/build/parser/ast.js.map +1 -0
- package/build/parser/lexer.d.ts +24 -0
- package/build/parser/lexer.d.ts.map +1 -0
- package/build/parser/lexer.js +168 -0
- package/build/parser/lexer.js.map +1 -0
- package/build/parser/parser.d.ts +14 -0
- package/build/parser/parser.d.ts.map +1 -0
- package/build/parser/parser.js +115 -0
- package/build/parser/parser.js.map +1 -0
- package/docs/plans/2025-02-24-mcp-calculator-design.md +344 -0
- package/docs/plans/2025-02-24-mcp-calculator-implementation.md +2626 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/design.md +46 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/proposal.md +21 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/specs/ascii-conversion/spec.md +22 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/tasks.md +24 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/ascii-conversion/spec.md +43 -0
- package/package.json +40 -0
- package/src/engines/decimal.ts +69 -0
- package/src/engines/programmer.ts +112 -0
- package/src/errors/handler.ts +55 -0
- package/src/errors/types.ts +37 -0
- package/src/index.ts +20 -0
- package/src/mcp/server.ts +287 -0
- package/src/mcp/tools/ascii.ts +116 -0
- package/src/mcp/tools/basic.ts +44 -0
- package/src/mcp/tools/conversion.ts +95 -0
- package/src/mcp/tools/programmer.ts +36 -0
- package/src/parser/ast.ts +51 -0
- package/src/parser/lexer.ts +216 -0
- package/src/parser/parser.ts +154 -0
- package/test/integration/ascii.test.ts +450 -0
- package/test/integration/basic-calculate.test.ts +272 -0
- package/test/integration/conversion.test.ts +357 -0
- package/test/integration/programmer-calculate.test.ts +363 -0
- package/test/unit/decimal-engine.test.ts +134 -0
- package/test/unit/error-handler.test.ts +173 -0
- package/test/unit/lexer.test.ts +176 -0
- package/test/unit/parser.test.ts +197 -0
- package/test/unit/programmer-engine.test.ts +234 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
当前 ASCII 转换工具有三个:
|
|
4
|
+
- `ascii_to_number`: 文本 → 逗号分隔的代码字符串
|
|
5
|
+
- `number_to_ascii`: 单个代码 → 字符
|
|
6
|
+
- `multiple_ascii_to_number`: 代码数组 → 文本(命名方向错误)
|
|
7
|
+
|
|
8
|
+
项目使用 TypeScript + MCP Server 架构,工具层在 `src/mcp/tools/ascii.ts`,注册在 `src/mcp/server.ts`。
|
|
9
|
+
|
|
10
|
+
## Goals / Non-Goals
|
|
11
|
+
|
|
12
|
+
**Goals:**
|
|
13
|
+
- 修复 `multiple_ascii_to_number` 的命名问题
|
|
14
|
+
- 简化 API,用单一工具覆盖单个和批量转换场景
|
|
15
|
+
- 保持与现有 `ascii_to_number` 的功能对称性
|
|
16
|
+
|
|
17
|
+
**Non-Goals:**
|
|
18
|
+
- 不改变现有工具的输出格式
|
|
19
|
+
- 不新增额外的工具
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
|
|
23
|
+
**合并而非重命名**
|
|
24
|
+
- 将 `multiple_ascii_to_number` 的功能合并到 `number_to_ascii`
|
|
25
|
+
- 理由: 减少工具数量,API 更简洁
|
|
26
|
+
- 替代方案考虑过: 重命名为 `multiple_number_to_ascii`(会增加工具数量)
|
|
27
|
+
|
|
28
|
+
**输入参数设计**
|
|
29
|
+
```typescript
|
|
30
|
+
{
|
|
31
|
+
code?: number, // 单个代码
|
|
32
|
+
codes?: number[] // 代码数组
|
|
33
|
+
}
|
|
34
|
+
// 两个参数都是可选,调用者选择其中一个
|
|
35
|
+
```
|
|
36
|
+
- 理由: 允许灵活的单/批量调用,不破坏现有单代码调用
|
|
37
|
+
- 替代方案考虑过: 使用联合类型 `code: number | number[]`(但 JSON Schema 不支持)
|
|
38
|
+
|
|
39
|
+
**删除而非重命名函数**
|
|
40
|
+
- 直接删除 `multipleAsciiToNumber` 函数
|
|
41
|
+
- 理由: 不需要向后兼容,简化代码
|
|
42
|
+
|
|
43
|
+
## Risks / Trade-offs
|
|
44
|
+
|
|
45
|
+
**参数验证复杂度** → 需要同时检查 `code` 和 `codes` 参数,确保恰好提供一个
|
|
46
|
+
**JSON Schema 限制** → 无法使用 TypeScript 联合类型,但通过可选参数模式解决
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
当前 MCP 工具中的 `multiple_ascii_to_number` 命名与实际功能不匹配。该工具接受 `codes: number[]` 参数并返回文本字符串,实际转换方向是 Number → ASCII,但名字暗示相反方向。这会导致用户混淆。
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- **删除** 工具 `multiple_ascii_to_number`
|
|
8
|
+
- **扩展** 工具 `number_to_ascii` 同时支持单个数字和数字数组输入
|
|
9
|
+
- 单个数字: `code: number` → 字符串
|
|
10
|
+
- 数字数组: `codes: number[]` → 合并字符串
|
|
11
|
+
|
|
12
|
+
## Capabilities
|
|
13
|
+
|
|
14
|
+
### Modified Capabilities
|
|
15
|
+
- `ascii-conversion`: ASCII 转换工具的行为变更,`number_to_ascii` 将接受数组输入
|
|
16
|
+
|
|
17
|
+
## Impact
|
|
18
|
+
|
|
19
|
+
- `src/mcp/tools/ascii.ts`: 删除 `multipleAsciiToNumber` 函数,扩展 `numberToAscii` 函数
|
|
20
|
+
- `src/mcp/server.ts`: 删除 `multiple_ascii_to_number` 工具注册,修改 `number_to_ascii` 输入 schema
|
|
21
|
+
- 测试文件: 更新相关测试用例
|
package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/specs/ascii-conversion/spec.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## 修改的需求
|
|
2
|
+
|
|
3
|
+
### 需求: 数字转 ASCII 字符
|
|
4
|
+
系统必须将 ASCII/Unicode 码位转换为对应的字符。
|
|
5
|
+
|
|
6
|
+
#### 场景: 单个码位转换
|
|
7
|
+
- **当** 调用者提供 `code` 参数(单个数值)
|
|
8
|
+
- **则** 系统返回对应的字符字符串
|
|
9
|
+
|
|
10
|
+
#### 场景: 多个码位转换
|
|
11
|
+
- **当** 调用者提供 `codes` 参数(数值数组)
|
|
12
|
+
- **则** 系统返回所有对应字符拼接的字符串
|
|
13
|
+
|
|
14
|
+
#### 场景: 无效码位
|
|
15
|
+
- **当** 调用者提供的码位超出有效范围(BMP 范围为 0-65535)
|
|
16
|
+
- **则** 系统返回错误并附带描述信息
|
|
17
|
+
|
|
18
|
+
## 移除的需求
|
|
19
|
+
|
|
20
|
+
### 需求: 多个 ASCII 转数字工具
|
|
21
|
+
**原因**: 功能已合并到 `number_to_ascii` 工具以简化 API
|
|
22
|
+
**迁移**: 使用 `number_to_ascii` 的 `codes` 参数代替
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## 1. 更新 src/mcp/tools/ascii.ts
|
|
2
|
+
|
|
3
|
+
- [x] 1.1 删除 `MultipleAsciiToNumberInput` 接口
|
|
4
|
+
- [x] 1.2 删除 `multipleAsciiToNumber` 函数
|
|
5
|
+
- [x] 1.3 扩展 `NumberToAsciiInput` 接口,添加可选的 `codes?: number[]` 参数
|
|
6
|
+
- [x] 1.4 修改 `numberToAscii` 函数逻辑:
|
|
7
|
+
- 检查 `code` 和 `codes` 参数,确保恰好提供一个
|
|
8
|
+
- 如果提供 `codes`,遍历数组并拼接字符
|
|
9
|
+
- 如果提供 `code`,保持原有逻辑
|
|
10
|
+
|
|
11
|
+
## 2. 更新 src/mcp/server.ts
|
|
12
|
+
|
|
13
|
+
- [x] 2.1 从导入中删除 `multipleAsciiToNumber`
|
|
14
|
+
- [x] 2.2 在 TOOLS 数组中删除 `multiple_ascii_to_number` 工具定义
|
|
15
|
+
- [x] 2.3 修改 `number_to_ascii` 工具的 inputSchema,添加 `codes` 属性
|
|
16
|
+
- [x] 2.4 在 CallToolRequestSchema 处理器中删除 `multiple_ascii_to_number` case
|
|
17
|
+
- [x] 2.5 更新 `number_to_ascii` case 处理逻辑,支持 `codes` 参数
|
|
18
|
+
|
|
19
|
+
## 3. 更新测试
|
|
20
|
+
|
|
21
|
+
- [x] 3.1 删除 `multiple_ascii_to_number` 相关测试
|
|
22
|
+
- [x] 3.2 添加 `number_to_ascii` 批量转换测试用例
|
|
23
|
+
- [x] 3.3 添加参数验证测试(同时提供 code 和 codes 应返回错误)
|
|
24
|
+
- [x] 3.4 运行测试确认通过
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
|
|
3
|
+
# Project context (optional)
|
|
4
|
+
# This is shown to AI when creating artifacts.
|
|
5
|
+
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
|
6
|
+
# Example:
|
|
7
|
+
# context: |
|
|
8
|
+
# Tech stack: TypeScript, React, Node.js
|
|
9
|
+
# We use conventional commits
|
|
10
|
+
# Domain: e-commerce platform
|
|
11
|
+
|
|
12
|
+
# Per-artifact rules (optional)
|
|
13
|
+
# Add custom rules for specific artifacts.
|
|
14
|
+
# Example:
|
|
15
|
+
# rules:
|
|
16
|
+
# proposal:
|
|
17
|
+
# - Keep proposals under 500 words
|
|
18
|
+
# - Always include a "Non-goals" section
|
|
19
|
+
# tasks:
|
|
20
|
+
# - Break tasks into chunks of max 2 hours
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ASCII Conversion 规范
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
提供 ASCII/Unicode 码位与字符之间的双向转换功能,支持单个和批量转换。
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
### 需求: ASCII 字符转数字
|
|
10
|
+
|
|
11
|
+
系统必须将文本字符串转换为对应的 ASCII/Unicode 码位列表。
|
|
12
|
+
|
|
13
|
+
#### 场景: 基本文本转换
|
|
14
|
+
- **当** 调用者提供有效的非空文本字符串
|
|
15
|
+
- **则** 系统返回每个字符对应的 ASCII 码位,以逗号分隔
|
|
16
|
+
|
|
17
|
+
#### 场景: 空文本输入
|
|
18
|
+
- **当** 调用者提供空字符串或 null
|
|
19
|
+
- **则** 系统返回错误并提示文本不能为空
|
|
20
|
+
|
|
21
|
+
### 需求: 数字转 ASCII 字符
|
|
22
|
+
|
|
23
|
+
系统必须将 ASCII/Unicode 码位转换为对应的字符。
|
|
24
|
+
|
|
25
|
+
#### 场景: 单个码位转换
|
|
26
|
+
- **当** 调用者提供 `code` 参数(单个数值)
|
|
27
|
+
- **则** 系统返回对应的字符字符串
|
|
28
|
+
|
|
29
|
+
#### 场景: 多个码位转换
|
|
30
|
+
- **当** 调用者提供 `codes` 参数(数值数组)
|
|
31
|
+
- **则** 系统返回所有对应字符拼接的字符串
|
|
32
|
+
|
|
33
|
+
#### 场景: 无效码位
|
|
34
|
+
- **当** 调用者提供的码位超出有效范围(BMP 范围为 0-65535)
|
|
35
|
+
- **则** 系统返回错误并附带描述信息
|
|
36
|
+
|
|
37
|
+
#### 场景: 参数冲突
|
|
38
|
+
- **当** 调用者同时提供 `code` 和 `codes` 参数
|
|
39
|
+
- **则** 系统返回错误并提示只能提供一个参数
|
|
40
|
+
|
|
41
|
+
#### 场景: 缺少参数
|
|
42
|
+
- **当** 调用者未提供 `code` 或 `codes` 参数
|
|
43
|
+
- **则** 系统返回错误并提示必须提供一个参数
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "calc-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP Server for calculator with basic and programmer operations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"calc-mcp-server": "build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:ui": "vitest --ui",
|
|
15
|
+
"test:coverage": "vitest --coverage",
|
|
16
|
+
"lint": "eslint src --ext .ts",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"calculator",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
28
|
+
"decimal.js": "^10.4.3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.11.0",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
33
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
34
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
35
|
+
"@vitest/ui": "^4.0.18",
|
|
36
|
+
"eslint": "^8.56.0",
|
|
37
|
+
"typescript": "^5.3.3",
|
|
38
|
+
"vitest": "^4.0.18"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Decimal } from 'decimal.js';
|
|
2
|
+
import { ASTNode, BinaryOpNode, UnaryOpNode, LiteralNode, GroupNode } from '../parser/ast.js';
|
|
3
|
+
|
|
4
|
+
export class DecimalEngine {
|
|
5
|
+
|
|
6
|
+
evaluate(ast: ASTNode): Decimal {
|
|
7
|
+
return this.visit(ast);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private visit(node: ASTNode): Decimal {
|
|
11
|
+
switch (node.type) {
|
|
12
|
+
case 'Literal':
|
|
13
|
+
return this.visitLiteral(node as LiteralNode);
|
|
14
|
+
case 'BinaryOp':
|
|
15
|
+
return this.visitBinaryOp(node as BinaryOpNode);
|
|
16
|
+
case 'UnaryOp':
|
|
17
|
+
return this.visitUnaryOp(node as UnaryOpNode);
|
|
18
|
+
case 'Group':
|
|
19
|
+
return this.visit((node as GroupNode).expression);
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`Unknown node type: ${(node as any).type}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private visitLiteral(node: LiteralNode): Decimal {
|
|
26
|
+
// Strip prefixes for non-decimal literals
|
|
27
|
+
const value = node.value.replace(/^(0x|0b|0o)/, '');
|
|
28
|
+
return new Decimal(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private visitBinaryOp(node: BinaryOpNode): Decimal {
|
|
32
|
+
const left = this.visit(node.left);
|
|
33
|
+
const right = this.visit(node.right);
|
|
34
|
+
|
|
35
|
+
switch (node.operator) {
|
|
36
|
+
case '+':
|
|
37
|
+
return left.plus(right);
|
|
38
|
+
case '-':
|
|
39
|
+
return left.minus(right);
|
|
40
|
+
case '*':
|
|
41
|
+
return left.times(right);
|
|
42
|
+
case '/':
|
|
43
|
+
if (right.isZero()) {
|
|
44
|
+
throw new Error('DivisionByZero');
|
|
45
|
+
}
|
|
46
|
+
return left.dividedBy(right);
|
|
47
|
+
case '%':
|
|
48
|
+
if (right.isZero()) {
|
|
49
|
+
throw new Error('DivisionByZero');
|
|
50
|
+
}
|
|
51
|
+
return left.modulo(right);
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`不支持运算符 "${node.operator}",请使用程序员计算器进行此运算。`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private visitUnaryOp(node: UnaryOpNode): Decimal {
|
|
58
|
+
const operand = this.visit(node.operand);
|
|
59
|
+
|
|
60
|
+
switch (node.operator) {
|
|
61
|
+
case '-':
|
|
62
|
+
return operand.negated();
|
|
63
|
+
case '+':
|
|
64
|
+
return operand;
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unsupported unary operator for decimal engine: ${node.operator}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ASTNode, BinaryOpNode, UnaryOpNode, LiteralNode, GroupNode } from '../parser/ast.js';
|
|
2
|
+
import { ErrorHandler } from '../errors/handler.js';
|
|
3
|
+
|
|
4
|
+
export class ProgrammerEngine {
|
|
5
|
+
private errorHandler = new ErrorHandler();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Evaluates an AST node and returns the result as a bigint.
|
|
9
|
+
*
|
|
10
|
+
* @param ast - The AST node to evaluate
|
|
11
|
+
* @returns The evaluation result as a bigint
|
|
12
|
+
* @throws {RuntimeError} If division by zero is attempted
|
|
13
|
+
* @throws {RuntimeError} If floating point numbers are used
|
|
14
|
+
*/
|
|
15
|
+
evaluate(ast: ASTNode): bigint {
|
|
16
|
+
return this.visit(ast);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private visit(node: ASTNode): bigint {
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case 'Literal':
|
|
22
|
+
return this.visitLiteral(node as LiteralNode);
|
|
23
|
+
case 'BinaryOp':
|
|
24
|
+
return this.visitBinaryOp(node as BinaryOpNode);
|
|
25
|
+
case 'UnaryOp':
|
|
26
|
+
return this.visitUnaryOp(node as UnaryOpNode);
|
|
27
|
+
case 'Group':
|
|
28
|
+
return this.visit((node as GroupNode).expression);
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown node type: ${(node as any).type}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private visitLiteral(node: LiteralNode): bigint {
|
|
35
|
+
const { value, tokenType } = node;
|
|
36
|
+
|
|
37
|
+
switch (tokenType) {
|
|
38
|
+
case 'NUMBER':
|
|
39
|
+
// Decimal integer
|
|
40
|
+
if (value.includes('.')) {
|
|
41
|
+
throw this.errorHandler.runtimeError('InvalidNumber', 'Floating point numbers not supported in programmer mode');
|
|
42
|
+
}
|
|
43
|
+
return BigInt(value);
|
|
44
|
+
case 'HEX':
|
|
45
|
+
return BigInt(value);
|
|
46
|
+
case 'BINARY':
|
|
47
|
+
return BigInt(value);
|
|
48
|
+
case 'OCTAL':
|
|
49
|
+
return BigInt(value);
|
|
50
|
+
case 'CHAR_LITERAL':
|
|
51
|
+
// Parse char literal like 'A' or '\n'
|
|
52
|
+
const char = value.substring(1, value.length - 1);
|
|
53
|
+
return BigInt(char.charCodeAt(0));
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Unsupported literal type: ${tokenType}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private visitBinaryOp(node: BinaryOpNode): bigint {
|
|
60
|
+
const left = this.visit(node.left);
|
|
61
|
+
const right = this.visit(node.right);
|
|
62
|
+
|
|
63
|
+
switch (node.operator) {
|
|
64
|
+
case '+':
|
|
65
|
+
return left + right;
|
|
66
|
+
case '-':
|
|
67
|
+
return left - right;
|
|
68
|
+
case '*':
|
|
69
|
+
return left * right;
|
|
70
|
+
case '/':
|
|
71
|
+
if (right === 0n) {
|
|
72
|
+
throw this.errorHandler.runtimeError('DivisionByZero', 'Cannot divide by zero');
|
|
73
|
+
}
|
|
74
|
+
return left / right;
|
|
75
|
+
case '%':
|
|
76
|
+
if (right === 0n) {
|
|
77
|
+
throw this.errorHandler.runtimeError('DivisionByZero', 'Cannot divide by zero');
|
|
78
|
+
}
|
|
79
|
+
return left % right;
|
|
80
|
+
case '&':
|
|
81
|
+
return left & right;
|
|
82
|
+
case '|':
|
|
83
|
+
return left | right;
|
|
84
|
+
case '^':
|
|
85
|
+
return left ^ right;
|
|
86
|
+
case '<<':
|
|
87
|
+
return left << right;
|
|
88
|
+
case '>>':
|
|
89
|
+
return left >> right;
|
|
90
|
+
case '>>>':
|
|
91
|
+
// Unsigned right shift - convert to number, shift, back to bigint
|
|
92
|
+
return BigInt(Number(left) >>> Number(right));
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`Unsupported operator for programmer engine: ${node.operator}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private visitUnaryOp(node: UnaryOpNode): bigint {
|
|
99
|
+
const operand = this.visit(node.operand);
|
|
100
|
+
|
|
101
|
+
switch (node.operator) {
|
|
102
|
+
case '~':
|
|
103
|
+
return ~operand;
|
|
104
|
+
case '-':
|
|
105
|
+
return -operand;
|
|
106
|
+
case '+':
|
|
107
|
+
return operand;
|
|
108
|
+
default:
|
|
109
|
+
throw new Error(`Unsupported unary operator for programmer engine: ${node.operator}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorType,
|
|
3
|
+
Position,
|
|
4
|
+
ErrorDetail,
|
|
5
|
+
ErrorResponse,
|
|
6
|
+
CalculationResponse,
|
|
7
|
+
SuccessResponse,
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
export type { ErrorType, CalculationResponse, ErrorResponse, SuccessResponse };
|
|
11
|
+
|
|
12
|
+
export class ErrorHandler {
|
|
13
|
+
private suggestions: Record<ErrorType, string> = {
|
|
14
|
+
SyntaxError: 'Check your expression syntax. Ensure operators and operands are correctly placed.',
|
|
15
|
+
DivisionByZero: 'Division by zero is not allowed. Check your denominators.',
|
|
16
|
+
InvalidNumber: 'The number format is invalid. Check for typos or unsupported formats.',
|
|
17
|
+
OverflowError: 'The number is too large or too small to process.',
|
|
18
|
+
InvalidBase: 'Invalid base specified. Supported bases are 2, 8, 10, and 16.',
|
|
19
|
+
UnmatchedParen: 'Unmatched parenthesis. Ensure every opening parenthesis has a closing one.',
|
|
20
|
+
InvalidOperator: 'Invalid operator for this operation. Check supported operators.',
|
|
21
|
+
EmptyInput: 'Expression cannot be empty. Please provide a valid expression.',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
syntaxError(message: string, start: number, end: number): ErrorResponse {
|
|
25
|
+
return this.createError('SyntaxError', message, { start, end });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
runtimeError(type: ErrorType, message: string): ErrorResponse {
|
|
29
|
+
return this.createError(type, message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
createError(
|
|
33
|
+
type: ErrorType,
|
|
34
|
+
message: string,
|
|
35
|
+
position?: Position
|
|
36
|
+
): ErrorResponse {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: {
|
|
40
|
+
type,
|
|
41
|
+
message,
|
|
42
|
+
position,
|
|
43
|
+
suggestion: this.suggestions[type],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
success(result: string, details?: SuccessResponse['details']): SuccessResponse {
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
result,
|
|
52
|
+
details,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ErrorType =
|
|
2
|
+
| 'SyntaxError'
|
|
3
|
+
| 'DivisionByZero'
|
|
4
|
+
| 'InvalidNumber'
|
|
5
|
+
| 'OverflowError'
|
|
6
|
+
| 'InvalidBase'
|
|
7
|
+
| 'UnmatchedParen'
|
|
8
|
+
| 'InvalidOperator'
|
|
9
|
+
| 'EmptyInput';
|
|
10
|
+
|
|
11
|
+
export interface Position {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ErrorDetail {
|
|
17
|
+
type: ErrorType;
|
|
18
|
+
message: string;
|
|
19
|
+
position?: Position;
|
|
20
|
+
suggestion: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SuccessResponse {
|
|
24
|
+
success: true;
|
|
25
|
+
result: string;
|
|
26
|
+
details?: {
|
|
27
|
+
expression?: string;
|
|
28
|
+
steps?: string[];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ErrorResponse {
|
|
33
|
+
success: false;
|
|
34
|
+
error: ErrorDetail;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CalculationResponse = SuccessResponse | ErrorResponse;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createMCPServer } from './mcp/server.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const server = createMCPServer();
|
|
8
|
+
const transport = new StdioServerTransport();
|
|
9
|
+
|
|
10
|
+
await server.connect(transport);
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.error('MCP Calculator server running on stdio');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
main().catch((error) => {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.error('Fatal error:', error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|