@nshipster/sosumi 1.0.5 → 1.0.7

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/src/lib/mcp.ts CHANGED
@@ -9,46 +9,122 @@ import { searchAppleDeveloperDocs } from "./search"
9
9
  import { generateAppleDocUrl, normalizeDocumentationPath } from "./url"
10
10
  import { fetchVideoTranscriptMarkdown } from "./video"
11
11
 
12
+ export const MCP_SERVER_INFO = {
13
+ name: "sosumi.ai",
14
+ version: "1.0.0",
15
+ } as const
16
+
17
+ export interface ToolHttpBinding {
18
+ path?: string
19
+ pathFrom?: string
20
+ pathPrefix?: string
21
+ query?: Record<string, string>
22
+ }
23
+
24
+ export interface ToolMeta {
25
+ name: string
26
+ title: string
27
+ description: string
28
+ inputSchema: z.ZodRawShape
29
+ outputSchema?: z.ZodRawShape
30
+ annotations: {
31
+ readOnlyHint: boolean
32
+ destructiveHint: boolean
33
+ idempotentHint: boolean
34
+ openWorldHint: boolean
35
+ }
36
+ http: ToolHttpBinding
37
+ }
38
+
39
+ const searchResultSchema = z.object({
40
+ title: z.string().describe("Title of the documentation page"),
41
+ url: z.string().describe("Full URL to the documentation page"),
42
+ description: z.string().describe("Brief description of the page content"),
43
+ breadcrumbs: z.array(z.string()).describe("Navigation breadcrumbs showing the page hierarchy"),
44
+ tags: z.array(z.string()).describe("Tags associated with the page (languages, platforms, etc.)"),
45
+ type: z.string().describe("Type of result (documentation, general, etc.)"),
46
+ })
47
+
48
+ const readOnlyAnnotations = {
49
+ readOnlyHint: true,
50
+ destructiveHint: false,
51
+ idempotentHint: true,
52
+ openWorldHint: true,
53
+ } as const
54
+
55
+ export const TOOL_DEFINITIONS = {
56
+ searchAppleDocumentation: {
57
+ name: "searchAppleDocumentation",
58
+ title: "Search Apple Documentation",
59
+ description: "Search Apple Developer documentation and return structured results",
60
+ inputSchema: {
61
+ query: z.string().describe("Search query for Apple documentation"),
62
+ },
63
+ outputSchema: {
64
+ query: z.string().describe("The search query that was executed"),
65
+ results: z.array(searchResultSchema).describe("Array of search results"),
66
+ },
67
+ annotations: readOnlyAnnotations,
68
+ http: { path: "/search", query: { q: "query" } },
69
+ },
70
+ fetchAppleDocumentation: {
71
+ name: "fetchAppleDocumentation",
72
+ title: "Fetch Apple Documentation",
73
+ description:
74
+ "Fetch Apple Developer documentation and Human Interface Guidelines by path and return as markdown",
75
+ inputSchema: {
76
+ path: z
77
+ .string()
78
+ .describe(
79
+ "Documentation path (e.g., '/documentation/swift', '/documentation/swiftui/view', '/design/human-interface-guidelines/foundations/color')",
80
+ ),
81
+ },
82
+ annotations: readOnlyAnnotations,
83
+ http: { pathFrom: "path" },
84
+ },
85
+ fetchExternalDocumentation: {
86
+ name: "fetchExternalDocumentation",
87
+ title: "Fetch External Documentation",
88
+ description:
89
+ "Fetch external Swift-DocC documentation by absolute https URL and return as markdown",
90
+ inputSchema: {
91
+ url: z
92
+ .string()
93
+ .describe(
94
+ "External Swift-DocC URL (e.g., 'https://apple.github.io/swift-argument-parser/documentation/argumentparser')",
95
+ ),
96
+ },
97
+ annotations: readOnlyAnnotations,
98
+ http: { pathFrom: "url", pathPrefix: "/external/" },
99
+ },
100
+ fetchAppleVideoTranscript: {
101
+ name: "fetchAppleVideoTranscript",
102
+ title: "Fetch Apple Video Transcript",
103
+ description: "Fetch transcript for an Apple Developer video path and return as markdown",
104
+ inputSchema: {
105
+ path: z
106
+ .string()
107
+ .describe(
108
+ "Apple video path (e.g., '/videos/play/wwdc2021/10133' or '/videos/play/meet-with-apple/208')",
109
+ ),
110
+ },
111
+ annotations: readOnlyAnnotations,
112
+ http: { pathFrom: "path" },
113
+ },
114
+ } satisfies Record<string, ToolMeta>
115
+
12
116
  export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
13
- const server = new McpServer({
14
- name: "sosumi.ai",
15
- version: "1.0.0",
16
- })
117
+ const server = new McpServer(MCP_SERVER_INFO)
17
118
 
18
- // Register Apple search tool
119
+ const search = TOOL_DEFINITIONS.searchAppleDocumentation
19
120
  server.registerTool(
20
- "searchAppleDocumentation",
121
+ search.name,
21
122
  {
22
- title: "Search Apple Documentation",
23
- description: "Search Apple Developer documentation and return structured results",
24
- inputSchema: {
25
- query: z.string().describe("Search query for Apple documentation"),
26
- },
27
- outputSchema: {
28
- query: z.string().describe("The search query that was executed"),
29
- results: z
30
- .array(
31
- z.object({
32
- title: z.string().describe("Title of the documentation page"),
33
- url: z.string().describe("Full URL to the documentation page"),
34
- description: z.string().describe("Brief description of the page content"),
35
- breadcrumbs: z
36
- .array(z.string())
37
- .describe("Navigation breadcrumbs showing the page hierarchy"),
38
- tags: z
39
- .array(z.string())
40
- .describe("Tags associated with the page (languages, platforms, etc.)"),
41
- type: z.string().describe("Type of result (documentation, general, etc.)"),
42
- }),
43
- )
44
- .describe("Array of search results"),
45
- },
46
- annotations: {
47
- readOnlyHint: true,
48
- destructiveHint: false,
49
- idempotentHint: true,
50
- openWorldHint: true,
51
- },
123
+ title: search.title,
124
+ description: search.description,
125
+ inputSchema: search.inputSchema,
126
+ outputSchema: search.outputSchema,
127
+ annotations: search.annotations,
52
128
  },
53
129
  async ({ query }) => {
54
130
  try {
@@ -78,7 +154,6 @@ export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
78
154
  }
79
155
  }
80
156
 
81
- // Provide a readable text summary
82
157
  const resultText =
83
158
  `Found ${searchResponse.results.length} result(s) for "${query}":\n\n` +
84
159
  searchResponse.results
@@ -100,50 +175,34 @@ export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
100
175
  } catch (error) {
101
176
  const errorMessage = error instanceof Error ? error.message : "Unknown error"
102
177
 
103
- const structuredContent = {
104
- query,
105
- results: [],
106
- }
107
-
178
+ // Report the failure as an error rather than as structured content.
179
+ // An empty `results` array reads as "this documentation does not exist",
180
+ // which would leave the caller trusting its own memory instead of looking again.
108
181
  return {
182
+ isError: true,
109
183
  content: [
110
184
  {
111
185
  type: "text" as const,
112
186
  text: `Error searching Apple Developer documentation: ${errorMessage}`,
113
187
  },
114
188
  ],
115
- structuredContent,
116
189
  }
117
190
  }
118
191
  },
119
192
  )
120
193
 
121
- // Register documentation fetch tool (supports both dev docs and HIG)
194
+ const fetchDocs = TOOL_DEFINITIONS.fetchAppleDocumentation
122
195
  server.registerTool(
123
- "fetchAppleDocumentation",
196
+ fetchDocs.name,
124
197
  {
125
- title: "Fetch Apple Documentation",
126
- description:
127
- "Fetch Apple Developer documentation and Human Interface Guidelines by path and return as markdown",
128
- inputSchema: {
129
- path: z
130
- .string()
131
- .describe(
132
- "Documentation path (e.g., '/documentation/swift', 'swiftui/view', 'design/human-interface-guidelines/foundations/color')",
133
- ),
134
- },
135
- annotations: {
136
- readOnlyHint: true,
137
- destructiveHint: false,
138
- idempotentHint: true,
139
- openWorldHint: true,
140
- },
198
+ title: fetchDocs.title,
199
+ description: fetchDocs.description,
200
+ inputSchema: fetchDocs.inputSchema,
201
+ annotations: fetchDocs.annotations,
141
202
  },
142
203
  async ({ path }) => {
143
204
  try {
144
- // Check if this is a HIG path
145
205
  if (path.includes("design/human-interface-guidelines")) {
146
- // Handle HIG content
147
206
  const higPath = path.replace(/^\/?(design\/human-interface-guidelines\/)/, "")
148
207
  const sourceUrl = `https://developer.apple.com/design/human-interface-guidelines/${higPath}`
149
208
 
@@ -162,26 +221,25 @@ export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
162
221
  },
163
222
  ],
164
223
  }
165
- } else {
166
- // Handle regular developer documentation
167
- const normalizedPath = normalizeDocumentationPath(path)
168
- const appleUrl = generateAppleDocUrl(normalizedPath)
224
+ }
169
225
 
170
- const jsonData = await fetchJSONData(normalizedPath)
171
- const markdown = await renderFromJSON(jsonData, appleUrl)
226
+ const normalizedPath = normalizeDocumentationPath(path)
227
+ const appleUrl = generateAppleDocUrl(normalizedPath)
172
228
 
173
- if (!markdown || markdown.trim().length < 100) {
174
- throw new Error("Insufficient content in documentation")
175
- }
229
+ const jsonData = await fetchJSONData(normalizedPath)
230
+ const markdown = await renderFromJSON(jsonData, appleUrl)
176
231
 
177
- return {
178
- content: [
179
- {
180
- type: "text" as const,
181
- text: markdown,
182
- },
183
- ],
184
- }
232
+ if (!markdown || markdown.trim().length < 100) {
233
+ throw new Error("Insufficient content in documentation")
234
+ }
235
+
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text" as const,
240
+ text: markdown,
241
+ },
242
+ ],
185
243
  }
186
244
  } catch (error) {
187
245
  const errorMessage = error instanceof Error ? error.message : "Unknown error"
@@ -198,26 +256,14 @@ export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
198
256
  },
199
257
  )
200
258
 
201
- // Register external documentation fetch tool
259
+ const fetchExternal = TOOL_DEFINITIONS.fetchExternalDocumentation
202
260
  server.registerTool(
203
- "fetchExternalDocumentation",
261
+ fetchExternal.name,
204
262
  {
205
- title: "Fetch External Documentation",
206
- description:
207
- "Fetch external Swift-DocC documentation by absolute https URL and return as markdown",
208
- inputSchema: {
209
- url: z
210
- .string()
211
- .describe(
212
- "External Swift-DocC URL (e.g., 'https://apple.github.io/swift-argument-parser/documentation/argumentparser')",
213
- ),
214
- },
215
- annotations: {
216
- readOnlyHint: true,
217
- destructiveHint: false,
218
- idempotentHint: true,
219
- openWorldHint: true,
220
- },
263
+ title: fetchExternal.title,
264
+ description: fetchExternal.description,
265
+ inputSchema: fetchExternal.inputSchema,
266
+ annotations: fetchExternal.annotations,
221
267
  },
222
268
  async ({ url }) => {
223
269
  try {
@@ -250,25 +296,14 @@ export function createMcpServer(externalPolicyEnv: ExternalPolicyEnv = {}) {
250
296
  },
251
297
  )
252
298
 
253
- // Register Apple video transcript fetch tool
299
+ const fetchVideo = TOOL_DEFINITIONS.fetchAppleVideoTranscript
254
300
  server.registerTool(
255
- "fetchAppleVideoTranscript",
301
+ fetchVideo.name,
256
302
  {
257
- title: "Fetch Apple Video Transcript",
258
- description: "Fetch transcript for an Apple Developer video path and return as markdown",
259
- inputSchema: {
260
- path: z
261
- .string()
262
- .describe(
263
- "Apple video path (e.g., '/videos/play/wwdc2021/10133' or '/videos/play/meet-with-apple/208')",
264
- ),
265
- },
266
- annotations: {
267
- readOnlyHint: true,
268
- destructiveHint: false,
269
- idempotentHint: true,
270
- openWorldHint: true,
271
- },
303
+ title: fetchVideo.title,
304
+ description: fetchVideo.description,
305
+ inputSchema: fetchVideo.inputSchema,
306
+ annotations: fetchVideo.annotations,
272
307
  },
273
308
  async ({ path }) => {
274
309
  try {