MCPcopy
hub / github.com/npmx-dev/npmx.dev / handleSkillFile

Function handleSkillFile

server/routes/skills/[...pkg].get.ts:107–150  ·  view source on GitHub ↗
(
  event: H3Event,
  packageName: string,
  version: string,
  skillName: string,
  filePath: string,
)

Source from the content-addressed store, hash-verified

105}
106
107async function handleSkillFile(
108 event: H3Event,
109 packageName: string,
110 version: string,
111 skillName: string,
112 filePath: string,
113): Promise<string> {
114 // Validate file path to prevent directory traversal
115 if (filePath.includes('..') || filePath.startsWith('/')) {
116 throw createError({ statusCode: 400, message: 'Invalid file path' })
117 }
118
119 // Only allow files within skill subdirectories (scripts/, references/, assets/)
120 const allowedPrefixes = ['scripts/', 'references/', 'assets/', 'refs/']
121 if (!allowedPrefixes.some(p => filePath.startsWith(p))) {
122 throw createError({
123 statusCode: 400,
124 message: 'File must be in scripts/, references/, or assets/ subdirectory',
125 })
126 }
127
128 try {
129 const content = await fetchSkillFile(packageName, version, `skills/${skillName}/${filePath}`)
130
131 const ext = filePath.split('.').pop()?.toLowerCase() || ''
132 const contentTypes: Record<string, string> = {
133 md: 'text/markdown',
134 txt: 'text/plain',
135 json: 'application/json',
136 js: 'text/javascript',
137 ts: 'text/typescript',
138 sh: 'text/x-shellscript',
139 py: 'text/x-python',
140 }
141 setHeader(event, 'Content-Type', contentTypes[ext] || 'text/plain')
142
143 return content
144 } catch (error) {
145 if (error && typeof error === 'object' && 'statusCode' in error && error.statusCode === 404) {
146 throw createError({ statusCode: 404, message: ERROR_SKILL_FILE_NOT_FOUND })
147 }
148 throw error
149 }
150}

Callers 1

[...pkg].get.tsFile · 0.85

Calls 1

fetchSkillFileFunction · 0.85

Tested by

no test coverage detected