Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/mcp-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: Build mcp
run: bun run --cwd packages/mcp build

- name: Smoke-test the published Node entrypoint
run: bun run --cwd packages/mcp smoke:node

- name: Test mcp
run: bun test --cwd packages/mcp

Expand Down
5 changes: 3 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@
"README.md"
],
"scripts": {
"build": "tsc --build",
"build": "tsc --build && bun ../../scripts/fix-node-esm-imports.ts dist",
"dev": "tsgo --build --watch",
"test": "bun test src",
"bench:registry": "bun run src/registry/__bench__/relations-resolver.bench.ts",
"bench:schema": "bun run src/schema/__bench__/node-parsers.bench.ts",
"prepublishOnly": "npm run build"
"smoke:node": "node --input-type=module -e \"await import('./dist/index.js')\"",
"prepublishOnly": "npm run build && npm run smoke:node"
},
"peerDependencies": {
"@react-three/drei": "^10",
Expand Down
3 changes: 3 additions & 0 deletions packages/mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- The published ESM output now resolves relative JavaScript imports under Node,
so `pascal-mcp` and package subpath exports no longer require Bun's
extensionless import fallback.
- Tool schemas in `tools/list` now declare the JSON Schema 2020-12 dialect
instead of the SDK default `draft-07`, so clients that enforce 2020-12 no
longer reject every tool call.
Expand Down
5 changes: 3 additions & 2 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@
"CHANGELOG.md"
],
"scripts": {
"build": "tsc --build",
"build": "tsc --build && bun ../../scripts/fix-node-esm-imports.ts ../core/dist && bun ../../scripts/fix-node-esm-imports.ts dist",
"dev": "tsgo --build --watch",
"start": "bun dist/bin/pascal-mcp.js",
"test": "bun test",
"smoke": "bun run scripts/smoke.ts",
"prepublishOnly": "bun run build && bun test"
"smoke:node": "node dist/bin/pascal-mcp.js --help",
"prepublishOnly": "bun run build && bun test && bun run smoke:node"
Comment thread
Aymericr marked this conversation as resolved.
},
"peerDependencies": {
"@pascal-app/core": "^1.0.0"
Expand Down
81 changes: 81 additions & 0 deletions scripts/fix-node-esm-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import ts from 'typescript'

const distDir = resolve(process.cwd(), process.argv[2] ?? 'dist')

function emittedModuleFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name)
if (entry.isDirectory()) return emittedModuleFiles(path)
return entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.d.ts'))
? [path]
: []
})
}

function moduleSpecifiers(sourceFile: ts.SourceFile): ts.StringLiteral[] {
const specifiers: ts.StringLiteral[] = []
const visit = (node: ts.Node) => {
if (
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
node.moduleSpecifier &&
ts.isStringLiteral(node.moduleSpecifier)
) {
specifiers.push(node.moduleSpecifier)
} else if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
node.arguments.length === 1 &&
ts.isStringLiteral(node.arguments[0]!)
) {
specifiers.push(node.arguments[0]!)
} else if (
ts.isImportTypeNode(node) &&
ts.isLiteralTypeNode(node.argument) &&
ts.isStringLiteral(node.argument.literal)
) {
specifiers.push(node.argument.literal)
}
ts.forEachChild(node, visit)
}
visit(sourceFile)
return specifiers
}

function resolvedSpecifier(file: string, specifier: string): string {
if (!specifier.startsWith('.') || /\.(?:[cm]?js|json|node)$/.test(specifier)) return specifier
const target = resolve(dirname(file), specifier)
const emittedExtension = file.endsWith('.d.ts') ? '.d.ts' : '.js'
if (existsSync(`${target}${emittedExtension}`)) return `${specifier}.js`
if (existsSync(join(target, `index${emittedExtension}`))) {
return `${specifier.replace(/\/$/, '')}/index.js`
}
throw new Error(`Cannot resolve extensionless ESM import ${specifier} from ${file}`)
}

for (const file of emittedModuleFiles(distDir)) {
const source = readFileSync(file, 'utf8')
const parsed = ts.createSourceFile(
file,
source,
ts.ScriptTarget.Latest,
true,
file.endsWith('.d.ts') ? ts.ScriptKind.TS : ts.ScriptKind.JS,
)
const replacements = moduleSpecifiers(parsed)
.map((node) => ({
start: node.getStart(parsed) + 1,
end: node.getEnd() - 1,
value: resolvedSpecifier(file, node.text),
}))
.filter((replacement) => source.slice(replacement.start, replacement.end) !== replacement.value)
.sort((a, b) => b.start - a.start)

let rewritten = source
for (const replacement of replacements) {
rewritten =
rewritten.slice(0, replacement.start) + replacement.value + rewritten.slice(replacement.end)
}
if (rewritten !== source) writeFileSync(file, rewritten)
}
Loading