Workers MCP
Official
by cloudflare
- src
- scripts
import fs from 'fs-extra'
import tsBlankSpace from 'ts-blank-space'
// @ts-ignore
import jsdoc from 'jsdoc-api'
import { EntrypointDoc, Param, Returns, StaticProperty } from './types'
import path from 'node:path'
import chalk from 'chalk'
import filter from 'just-filter-object'
type JSDocPoint = {
comment: string
meta: {
range: [number, number]
filename: string
lineno: number
columno: number
path: string
code: {
id: string
name: string
type: 'FunctionExpression' | 'ClassDeclaration' | 'MethodDefinition' | 'ClassProperty' | 'Literal'
paramnames: string[]
}
}
undocumented: boolean
classdesc?: string
description: string
name: string
longname: string
kind: 'function' | 'member' | 'class'
memberof?: string
scope: 'global' | 'static' | 'instance'
async?: boolean
params?: Array<{ type: { names: string[] }; description: string; name: string; optional?: boolean }>
returns?: Array<{ type: { names: string[] }; description: string }>
examples?: string[]
ignore?: boolean
tags?: Array<{ title: string; originalTitle: string; text: string; value: string }>
access?: 'private'
}
export async function generateDocs(filename: string) {
if (!filename) throw new Error(`Missing filename`)
const source = tsBlankSpace(fs.readFileSync(filename, 'utf8'))
const data = ((await jsdoc.explain({ source: source, cache: true })) as Array<JSDocPoint>)
// Pretend ignored points don't exist
.filter((point) => !point.ignore)
/* SEARCH FOR EXPORTED CLASSES */
const exported_classes: Record<string, EntrypointDoc> = {}
let default_export: { class_name: string; range: [number, number] } | undefined
for (const point of data) {
// console.dir(point, { depth: null })
// if (point.meta) console.log(source.substring(...point.meta.range))
if (point.kind === 'class' && point.meta?.code?.type === 'ClassDeclaration') {
let exported_as = undefined
let name = point.meta.code.name
if (name.startsWith('exports.')) {
name = name.slice('exports.'.length)
exported_as = name
} else if (name === 'module.exports') {
const raw = source.substring(...point.meta.range)
const match = raw.match(/class (\w+) (extends|{)/)
name = match ? match[1] : 'default'
exported_as = 'default'
default_export = { class_name: name, range: point.meta.range }
}
const proxy = point.tags?.find(({ title }) => title.startsWith(`do-proxy-`))
// console.dir(point, { depth: null })
exported_classes[name] = Object.assign(
exported_classes[name] || {},
filter(
{
exported_as,
description: point.classdesc! || null,
methods: [],
statics: {},
proxy: proxy ? { entrypoint: proxy.value, strategy: proxy.title.slice('do-proxy-'.length) } : undefined,
},
(_, v) => v !== undefined,
),
)
// console.log(exported_classes)
}
}
/* SEARCH FOR CLASS METHODS OR STATICS */
for (const point of data) {
// If it's a named export, we get `.memberof`. If it's a default export, we have to get creative
const memberof =
point.memberof === 'module.exports' ||
(!point.memberof && default_export && rangeWithin(point.meta?.range, default_export.range))
? default_export?.class_name
: point.memberof
/* CLASS METHODS */
if (point.kind === 'function' && point.meta?.code?.type === 'MethodDefinition' && memberof) {
const ex = exported_classes[memberof]
if (!ex) {
throw new Error(
`Missing memberof ${memberof}. Got ${JSON.stringify(point)}, had ${JSON.stringify(Object.keys(exported_classes))}`,
)
}
if (point.access === 'private') continue
let returns: Returns = null
if (point.returns) {
const [ret, ...rest] = point.returns
if (!ret || rest.length > 0 || ret.type?.names.length !== 1) {
console.log(`WARN: unexpected returns value for ${JSON.stringify(point)}`)
}
returns = { description: ret.description, type: ret.type.names[0] }
}
const params: Param[] = (point.params || [])
.map(({ description, type, name, optional }) => {
if (type.names.length !== 1) {
console.log(`WARN: unexpected params value for ${JSON.stringify(point)}`)
return null
}
return { description, name, type: type.names[0], optional }
})
.filter((p) => p !== null)
ex.methods.push({
name: point.name,
description: point.description,
params,
returns,
...(point.examples ? { examples: point.examples } : {}),
})
}
/* STATICS */
if (
point.kind === 'member' &&
point.meta?.code?.type === 'ClassProperty' &&
memberof &&
point.meta?.range &&
source.substring(...point.meta.range).match(/^\s*static\s/)
) {
// console.dir(point, { depth: null })
const ex = exported_classes[memberof]
if (!ex) {
throw new Error(
`Missing memberof ${memberof}. Got ${JSON.stringify(point)}, had ${JSON.stringify(Object.keys(exported_classes))}`,
)
}
const members: StaticProperty[] = []
// Sadly jsdoc-api doesn't give us any reference between the members of this static property and the
// static property itself. So we need to search the list of points for any that exist within the parent
// property's range. I don't love having to do a nested loop (algorithmic complexity O(honey)) but this
// is a POC and we're likely to replace jsdoc-api anyway get all the way off my back please.
for (const subpoint of data) {
if (subpoint.meta?.code?.id !== point.meta?.code?.id && rangeWithin(subpoint.meta?.range, point.meta.range)) {
// console.dir(subpoint, { depth: null })
const type = subpoint.meta?.code.type === 'Literal' ? 'string' : subpoint.returns?.[0]?.type?.names?.[0]
members.push({
name: subpoint.name,
description: subpoint.description,
type,
})
}
}
ex.statics[point.name] = members
}
}
/* SEARCH FOR EXPORT RENAMES */
for (const point of data) {
if (point.kind === 'member' && point.scope === 'global' && point.meta?.code?.name?.startsWith('exports.')) {
let name = point.name
const renamed = source.substring(...point.meta.range).match(/(\w+) as \w+/)
if (renamed) {
name = renamed[1]
}
if (exported_classes[name]) {
exported_classes[name].exported_as = point.name
} else {
console.log(`WARN: couldn't find which class to export for ${JSON.stringify(point)}`)
}
}
}
/* SEARCH FOR @do-proxy classes */
for (const [name, cls] of Object.entries(exported_classes)) {
if (cls.proxy) {
const target = exported_classes[cls.proxy.entrypoint]
if (!target)
console.log(`WARN: couldn't find which class to proxy for ${name}. Looking for ${cls.proxy.entrypoint}`)
for (const method of target.methods) {
cls.methods.push({
...method,
...(cls.proxy.strategy === 'prepend-session-id'
? {
params: [
{
type: 'string',
name: 'sessionID',
description:
'A unique identifier to be used for all tool calls during this conversation. Unless the user specifies explicitly which "session identifier" to use, the system should generate a completely random string of at least 8 characters.',
},
...method.params,
],
}
: {}),
})
}
// console.log(target.methods)
delete cls.proxy
}
}
await fs.ensureDir('dist')
await fs.writeFile(path.join('dist', 'docs.json'), JSON.stringify(exported_classes, null, 2))
console.log(`Generated docs for ${chalk.green(filename)} in ${chalk.yellow('dist/docs.json')}`)
console.log(
chalk.gray(
Object.entries(exported_classes)
.flatMap(([k, v]) => [
`• ${chalk.green(k)} exported as ${chalk.green(v.exported_as)}`,
...v.methods.map(
(m) =>
` - ${chalk.yellow(m.name)}(${m.params.map((p) => `${chalk.white(p.name)}: ${p.type || '?'}`).join(', ')}): ${m.returns?.type || '?'}`,
),
])
.join('\n'),
),
)
}
function rangeWithin(inner: [number, number] | undefined, outer: [number, number]) {
if (!inner) return false
const [a, b] = inner
const [x, y] = outer
return a >= x && b <= y
}