@nshipster/sosumi 1.0.6 → 1.0.8
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/README.md +44 -1
- package/package.json +9 -8
- package/public/index.html +3 -1
- package/public/llms.txt +1 -1
- package/public/robots.txt +1 -0
- package/public/webmcp.js +71 -0
- package/src/index.ts +112 -1
- package/src/lib/a2a.ts +90 -0
- package/src/lib/external/fetch.ts +3 -0
- package/src/lib/hig/render.ts +94 -73
- package/src/lib/markdown.ts +115 -0
- package/src/lib/mcp.ts +150 -115
- package/src/lib/reference/render.ts +112 -156
- package/src/lib/search.ts +115 -55
- package/src/lib/types.ts +12 -0
- package/src/lib/url.ts +35 -0
- package/src/lib/webbotauth.ts +261 -0
- package/src/lib/webmcp.ts +38 -0
- package/wrangler.jsonc +1 -1
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
|
-
|
|
119
|
+
const search = TOOL_DEFINITIONS.searchAppleDocumentation
|
|
19
120
|
server.registerTool(
|
|
20
|
-
|
|
121
|
+
search.name,
|
|
21
122
|
{
|
|
22
|
-
title:
|
|
23
|
-
description:
|
|
24
|
-
inputSchema:
|
|
25
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
194
|
+
const fetchDocs = TOOL_DEFINITIONS.fetchAppleDocumentation
|
|
122
195
|
server.registerTool(
|
|
123
|
-
|
|
196
|
+
fetchDocs.name,
|
|
124
197
|
{
|
|
125
|
-
title:
|
|
126
|
-
description:
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
}
|
|
166
|
-
// Handle regular developer documentation
|
|
167
|
-
const normalizedPath = normalizeDocumentationPath(path)
|
|
168
|
-
const appleUrl = generateAppleDocUrl(normalizedPath)
|
|
224
|
+
}
|
|
169
225
|
|
|
170
|
-
|
|
171
|
-
|
|
226
|
+
const normalizedPath = normalizeDocumentationPath(path)
|
|
227
|
+
const appleUrl = generateAppleDocUrl(normalizedPath)
|
|
172
228
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
229
|
+
const jsonData = await fetchJSONData(normalizedPath)
|
|
230
|
+
const markdown = await renderFromJSON(jsonData, appleUrl)
|
|
176
231
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
259
|
+
const fetchExternal = TOOL_DEFINITIONS.fetchExternalDocumentation
|
|
202
260
|
server.registerTool(
|
|
203
|
-
|
|
261
|
+
fetchExternal.name,
|
|
204
262
|
{
|
|
205
|
-
title:
|
|
206
|
-
description:
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
299
|
+
const fetchVideo = TOOL_DEFINITIONS.fetchAppleVideoTranscript
|
|
254
300
|
server.registerTool(
|
|
255
|
-
|
|
301
|
+
fetchVideo.name,
|
|
256
302
|
{
|
|
257
|
-
title:
|
|
258
|
-
description:
|
|
259
|
-
inputSchema:
|
|
260
|
-
|
|
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 {
|