Skip to content
Open
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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Apple Developer Documentation MCP Server - Access Apple's official developer doc

- 🔍 **Smart Search**: Intelligent search across Apple Developer Documentation for SwiftUI, UIKit, Foundation, CoreData, ARKit, and more
- 📚 **Complete Documentation Access**: Full access to Apple's JSON API for Swift, Objective-C, and framework documentation
- 🎨 **Apple Design and HIG Access**: Read Human Interface Guidelines JSON, Apple Design pages, and Design Resources catalog entries
- 🖼️ **Design Resource Previews**: Return Apple-provided HIG images and resource thumbnails as MCP image content blocks
- 📦 **Downloadable Design Resources**: Download direct Apple-hosted templates, fonts, tools, and archives into a local MCP resource cache
- 🔧 **Framework Index**: Browse hierarchical API structures for iOS, macOS, watchOS, tvOS, visionOS frameworks
- 📋 **Technology Catalog**: Explore Apple technologies including SwiftUI, UIKit, Metal, Core ML, Vision, and ARKit
- 📰 **Documentation Updates**: Track WWDC 2024/2025 announcements, iOS 26, macOS 26, and latest SDK releases
Expand Down Expand Up @@ -269,6 +272,15 @@ npm install && npm run build
"Get URLSession async/await methods"
```

### 🎨 Apple Design and HIG
```
"Search Apple Design docs for layout"
"Read the HIG page about color"
"List Apple Design Resources for iOS templates"
"Download the Apple Design resource with this resourceId"
"Show Apple Design examples for the layout HIG page"
```

### 🔧 Framework Exploration
```
"Show me SwiftUI framework API index"
Expand Down Expand Up @@ -366,6 +378,11 @@ npm install && npm run build
|------|-------------|--------------|
| `search_apple_docs` | Search Apple Developer Documentation | Official search API, find specific APIs, classes, methods |
| `get_apple_doc_content` | Get detailed documentation content | JSON API access, optional enhanced analysis (related/similar APIs, platform compatibility) |
| `search_apple_design_docs` | Search Apple Design and HIG content | HIG JSON references, Design pages, Design Resources catalog |
| `get_apple_design_content` | Read Apple Design and HIG pages | HIG JSON rendering, HTML fallback for `/design/` pages |
| `list_apple_design_resources` | List Apple Design Resources | Stable resource IDs, category/platform/format filters, previews and links |
| `download_apple_design_resource` | Download direct Apple Design resources | Local cache, MCP `resource_link` blocks, `resources/read` blob access |
| `get_apple_design_examples` | Return Apple Design visual examples | MCP `image` blocks with base64 data and MIME type |
| `list_technologies` | Browse all Apple technologies | Category filtering, language support, beta status |
| `search_framework_symbols` | Search symbols in specific framework | Classes, structs, protocols, wildcard patterns, type filtering |
| `get_related_apis` | Find related APIs | Inheritance, conformance, "See Also" relationships |
Expand All @@ -389,6 +406,7 @@ apple-docs-mcp/
│ ├── tools/ # MCP tool implementations
│ │ ├── search-parser.ts # HTML search result parsing
│ │ ├── doc-fetcher.ts # JSON API documentation fetching
│ │ ├── design-docs.ts # Apple Design, HIG, resources, and previews
│ │ ├── list-technologies.ts # Technology catalog handling
│ │ ├── get-documentation-updates.ts # Documentation updates tracking
│ │ ├── get-technology-overviews.ts # Technology overviews and guides
Expand Down Expand Up @@ -436,6 +454,10 @@ apple-docs-mcp/
| Framework Indexes | 1 hour | 100 entries | Stable structure, less frequent changes |
| Technologies List | 2 hours | 50 entries | Rarely changes, large content |
| Documentation Updates | 30 minutes | 100 entries | Regular updates, WWDC announcements |
| Apple Design Content | 2 hours | 100 entries | HIG and Design pages are stable during a session |
| Apple Design Resources | 2 hours | 20 entries | Catalog metadata changes less often than page reads |

Downloaded Apple Design files are cached outside the repository by default. Set `APPLE_DOCS_MCP_CACHE_DIR` to choose a cache directory for downloaded resource files exposed through MCP `resources/list` and `resources/read`.

## 📦 WWDC Data

Expand Down Expand Up @@ -555,4 +577,4 @@ Search Apple Developer Documentation | iOS Development | macOS Development | Swi

[Report Issues](https://github.com/kimsungwhee/apple-docs-mcp/issues) • [Request Features](https://github.com/kimsungwhee/apple-docs-mcp/issues/new) • [Documentation](https://github.com/kimsungwhee/apple-docs-mcp)

</div>
</div>
11 changes: 10 additions & 1 deletion src/__tests__/http-client-headers-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ describe('HTTP Client Headers Integration', () => {
expect(options.headers['User-Agent']).toBe('Custom-Agent/1.0');
expect(options.headers['Custom-Header']).toBe('custom-value');
});

test('should pass redirect handling options to fetch', async () => {
await httpClient.get('https://example.com/api', {
redirect: 'manual',
});

const [, options] = mockFetch.mock.calls[0];
expect(options.redirect).toBe('manual');
});
});

describe('getText with generated headers', () => {
Expand Down Expand Up @@ -232,4 +241,4 @@ describe('HTTP Client Headers Integration', () => {
expect(options.headers).toHaveProperty('Accept-Encoding');
});
});
});
});
142 changes: 136 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
type CallToolResult,
} from '@modelcontextprotocol/sdk/types.js';
import { parseSearchResults } from './tools/search-parser.js';
import { fetchAppleDocJson } from './tools/doc-fetcher.js';
import { handleListTechnologies } from './tools/list-technologies.js';
Expand All @@ -16,15 +22,34 @@ import { handleFindSimilarApis } from './tools/find-similar-apis.js';
import { handleGetDocumentationUpdates } from './tools/get-documentation-updates.js';
import { handleGetTechnologyOverviews } from './tools/get-technology-overviews.js';
import { handleGetSampleCode } from './tools/get-sample-code.js';
import {
handleDownloadAppleDesignResource,
handleGetAppleDesignContent,
handleGetAppleDesignExamples,
handleListAppleDesignResources,
handleSearchAppleDesignDocs,
listCachedDesignResources,
readCachedDesignResource,
} from './tools/design-docs.js';
import { APPLE_URLS } from './utils/constants.js';
import { isValidAppleDeveloperUrl } from './utils/url-converter.js';
import type { AppError } from './types/error.js';
import { isAppleDesignUrl, isValidAppleDeveloperUrl } from './utils/url-converter.js';
import { validateInput, ErrorType, createStandardErrorResponse, createToolErrorResponse } from './utils/error-handler.js';
import { httpClient } from './utils/http-client.js';
import { preloadPopularFrameworks } from './utils/preloader.js';
import { warmUpCaches, schedulePeriodicCacheRefresh } from './utils/cache-warmer.js';
import { logger } from './utils/logger.js';
import { API_LIMITS } from './utils/constants.js';

function isAppError(error: unknown): error is AppError {
return (
typeof error === 'object'
&& error !== null
&& 'type' in error
&& 'message' in error
);
}

export default class AppleDeveloperDocsMCPServer {
private server: Server;

Expand All @@ -34,7 +59,7 @@ export default class AppleDeveloperDocsMCPServer {
private async handleAsyncOperation<T>(
operation: () => Promise<T>,
operationName: string,
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
): Promise<CallToolResult> {
try {
const result = await operation();
return {
Expand All @@ -48,9 +73,23 @@ export default class AppleDeveloperDocsMCPServer {
} catch (error) {
// If error is already an AppError, use tool-specific suggestions
if (error && typeof error === 'object' && 'type' in error) {
return createToolErrorResponse(error as any, operationName);
return createToolErrorResponse(error as any, operationName) as CallToolResult;
}
return createStandardErrorResponse(error, operationName) as CallToolResult;
}
}

private async handleToolResultOperation(
operation: () => Promise<CallToolResult>,
operationName: string,
): Promise<CallToolResult> {
try {
return await operation();
} catch (error) {
if (isAppError(error)) {
return createToolErrorResponse(error, operationName) as CallToolResult;
}
return createStandardErrorResponse(error, operationName);
return createStandardErrorResponse(error, operationName) as CallToolResult;
}
}

Expand All @@ -63,11 +102,13 @@ export default class AppleDeveloperDocsMCPServer {
{
capabilities: {
tools: {},
resources: {},
},
},
);

this.setupTools();
this.setupResources();
this.setupErrorHandling();
}

Expand Down Expand Up @@ -107,6 +148,16 @@ export default class AppleDeveloperDocsMCPServer {
});
}

private setupResources() {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return await listCachedDesignResources();
});

this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
return await readCachedDesignResource(request.params.uri);
});
}

public async searchAppleDocs(query: string, type: string = 'all') {
try {
// 输入验证
Expand Down Expand Up @@ -155,6 +206,10 @@ export default class AppleDeveloperDocsMCPServer {
}, 'get_apple_doc_content');
}

if (isAppleDesignUrl(url)) {
return await this.getAppleDesignContent(url);
}

// fetchAppleDocJson 已经返回正确的MCP响应格式,直接返回
return await fetchAppleDocJson(url, {
includeRelatedApis,
Expand Down Expand Up @@ -182,6 +237,81 @@ export default class AppleDeveloperDocsMCPServer {
);
}

public async searchAppleDesignDocs(
query: string,
contentType: 'all' | 'hig' | 'resource' | 'page' = 'all',
platform: string = 'all',
limit: number = 20,
) {
return this.handleToolResultOperation(
() => handleSearchAppleDesignDocs({
query,
contentType,
platform,
limit,
}),
'search_apple_design_docs',
);
}

public async getAppleDesignContent(url: string) {
return this.handleToolResultOperation(
() => handleGetAppleDesignContent({ url }),
'get_apple_design_content',
);
}

public async listAppleDesignResources(
category?: string,
platform?: string,
format?: string,
searchQuery?: string,
limit: number = 50,
) {
return this.handleToolResultOperation(
() => handleListAppleDesignResources({
category,
platform,
format,
searchQuery,
limit,
}),
'list_apple_design_resources',
);
}

public async downloadAppleDesignResource(
resourceId?: string,
url?: string,
maxBytes?: number,
) {
return this.handleToolResultOperation(
() => handleDownloadAppleDesignResource({
resourceId,
url,
maxBytes,
}),
'download_apple_design_resource',
);
}

public async getAppleDesignExamples(
url?: string,
resourceId?: string,
query?: string,
limit: number = 3,
) {
return this.handleToolResultOperation(
() => handleGetAppleDesignExamples({
url,
resourceId,
query,
limit,
}),
'get_apple_design_examples',
);
}

public async searchFrameworkSymbols(framework: string, symbolType: string = 'all', namePattern?: string, language: string = 'swift', limit: number = API_LIMITS.DEFAULT_FRAMEWORK_SYMBOLS_LIMIT) {
return this.handleAsyncOperation(
() => searchFrameworkSymbols(framework, symbolType, namePattern, language, limit),
Expand Down Expand Up @@ -316,4 +446,4 @@ if (process.env.NODE_ENV !== 'test') {
logger.error('Fatal error in main():', error);
process.exit(1);
});
}
}
39 changes: 39 additions & 0 deletions src/schemas/design.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { z } from 'zod';

export const searchAppleDesignDocsSchema = z.object({
query: z.string().describe('Search query for Apple Design and HIG documentation'),
contentType: z.enum(['all', 'hig', 'resource', 'page']).default('all')
.describe('Type of Apple Design content to search'),
platform: z.string().default('all')
.describe('Platform filter such as iOS, iPadOS, macOS, watchOS, tvOS, or visionOS'),
limit: z.number().int().min(1).max(50).default(20)
.describe('Maximum number of results to return'),
});

export const getAppleDesignContentSchema = z.object({
url: z.url().describe('Apple Design URL to read'),
});

export const listAppleDesignResourcesSchema = z.object({
category: z.string().optional().describe('Resource category filter'),
platform: z.string().optional().describe('Platform or subsection filter'),
format: z.string().optional().describe('Resource format filter such as figma, sketch, dmg, or zip'),
searchQuery: z.string().optional().describe('Search query for resource titles, notes, and labels'),
limit: z.number().int().min(1).max(100).default(50)
.describe('Maximum number of resources to return'),
});

export const downloadAppleDesignResourceSchema = z.object({
resourceId: z.string().optional().describe('Resource ID returned by list_apple_design_resources'),
url: z.url().optional().describe('Direct Apple-hosted resource URL to download'),
maxBytes: z.number().int().min(1).max(250 * 1024 * 1024).optional()
.describe('Maximum download size in bytes'),
});

export const getAppleDesignExamplesSchema = z.object({
url: z.url().optional().describe('Apple Design, HIG, preview, or image URL'),
resourceId: z.string().optional().describe('Resource ID returned by list_apple_design_resources'),
query: z.string().optional().describe('Search query for examples and thumbnails'),
limit: z.number().int().min(1).max(10).default(3)
.describe('Maximum number of image examples to return'),
});
9 changes: 8 additions & 1 deletion src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ export { getPlatformCompatibilitySchema } from './platform-compatibility.schema.
export { findSimilarApisSchema } from './similar-apis.schema.js';
export { getDocumentationUpdatesSchema } from './documentation-updates.schema.js';
export { getTechnologyOverviewsSchema } from './technology-overviews.schema.js';
export { getSampleCodeSchema } from './sample-code.schema.js';
export { getSampleCodeSchema } from './sample-code.schema.js';
export {
searchAppleDesignDocsSchema,
getAppleDesignContentSchema,
listAppleDesignResourcesSchema,
downloadAppleDesignResourceSchema,
getAppleDesignExamplesSchema,
} from './design.schema.js';
Loading