Skip to content

Commit 0e85721

Browse files
Merge pull request #2 from stefanoamorelli/feat/document-endpoints
2 parents fe3d1db + f953477 commit 0e85721

File tree

10 files changed

+475
-1
lines changed

10 files changed

+475
-1
lines changed

src/api/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { OfficersApiClient } from './officers-api.js';
55
import { FilingApiClient } from './filing-api.js';
66
import { ChargesApiClient } from './charges-api.js';
77
import { PSCApiClient } from './psc-api.js';
8+
import { DocumentApiClient } from './document-api.js';
89

910
export class CompaniesHouseApiClient {
1011
public company: CompanyApiClient;
@@ -13,6 +14,7 @@ export class CompaniesHouseApiClient {
1314
public filing: FilingApiClient;
1415
public charges: ChargesApiClient;
1516
public psc: PSCApiClient;
17+
public document: DocumentApiClient;
1618

1719
constructor(config: ApiConfig) {
1820
this.company = new CompanyApiClient(config);
@@ -21,6 +23,7 @@ export class CompaniesHouseApiClient {
2123
this.filing = new FilingApiClient(config);
2224
this.charges = new ChargesApiClient(config);
2325
this.psc = new PSCApiClient(config);
26+
this.document = new DocumentApiClient(config);
2427
}
2528

2629
async testConnection(): Promise<boolean> {

src/api/document-api.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { BaseApiClient } from './base-client.js';
2+
import type {
3+
DocumentMetadata,
4+
DocumentContent,
5+
DocumentMetadataResponse
6+
} from '../types/document.js';
7+
8+
export class DocumentApiClient extends BaseApiClient {
9+
async getDocumentMetadata(params: DocumentMetadata): Promise<DocumentMetadataResponse> {
10+
const response = await this.client.get(`/document/${params.document_id}`);
11+
return response.data;
12+
}
13+
14+
async getDocumentContent(params: DocumentContent): Promise<Buffer> {
15+
const response = await this.client.get(`/document/${params.document_id}/content`, {
16+
responseType: 'arraybuffer',
17+
headers: {
18+
Accept: 'application/pdf'
19+
}
20+
});
21+
return Buffer.from(response.data);
22+
}
23+
}

src/handlers/document-handlers.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { CompaniesHouseApiClient } from '../api/client.js';
2+
import * as schemas from '../types/document.js';
3+
4+
export class DocumentHandlers {
5+
constructor(private apiClient: CompaniesHouseApiClient) {}
6+
7+
async handleGetDocumentMetadata(args: unknown) {
8+
const params = schemas.DocumentMetadataSchema.parse(args);
9+
const result = await this.apiClient.document.getDocumentMetadata(params);
10+
return {
11+
content: [
12+
{
13+
type: 'text',
14+
text: JSON.stringify(result, null, 2)
15+
}
16+
]
17+
};
18+
}
19+
20+
async handleGetDocumentContent(args: unknown) {
21+
const params = schemas.DocumentContentSchema.parse(args);
22+
const result = await this.apiClient.document.getDocumentContent(params);
23+
return {
24+
content: [
25+
{
26+
type: 'text',
27+
text: `Document content retrieved (${result.length} bytes). Content is binary PDF data.`
28+
}
29+
]
30+
};
31+
}
32+
}

src/mcp-server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { OfficersHandlers } from './handlers/officers-handlers.js';
99
import { FilingHandlers } from './handlers/filing-handlers.js';
1010
import { ChargesHandlers } from './handlers/charges-handlers.js';
1111
import { PSCHandlers } from './handlers/psc-handlers.js';
12+
import { DocumentHandlers } from './handlers/document-handlers.js';
1213

1314
export interface ServerConfig {
1415
apiKey: string;
@@ -24,6 +25,7 @@ export class CompaniesHouseMCPServer {
2425
private filingHandlers: FilingHandlers;
2526
private chargesHandlers: ChargesHandlers;
2627
private pscHandlers: PSCHandlers;
28+
private documentHandlers: DocumentHandlers;
2729

2830
constructor(config: ServerConfig) {
2931
this.server = new Server(
@@ -50,6 +52,7 @@ export class CompaniesHouseMCPServer {
5052
this.filingHandlers = new FilingHandlers(this.apiClient);
5153
this.chargesHandlers = new ChargesHandlers(this.apiClient);
5254
this.pscHandlers = new PSCHandlers(this.apiClient);
55+
this.documentHandlers = new DocumentHandlers(this.apiClient);
5356

5457
this.setupHandlers();
5558
}
@@ -146,6 +149,12 @@ export class CompaniesHouseMCPServer {
146149
case 'get_psc_super_secure':
147150
return await this.pscHandlers.handleGetPSCSuperSecure(args);
148151

152+
// Document endpoints
153+
case 'get_document_metadata':
154+
return await this.documentHandlers.handleGetDocumentMetadata(args);
155+
case 'get_document_content':
156+
return await this.documentHandlers.handleGetDocumentContent(args);
157+
149158
default:
150159
throw new Error(`Unknown tool: ${name}`);
151160
}

src/tools/tools-definition.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,13 +755,47 @@ export function getPSCTools(): Tool[] {
755755
];
756756
}
757757

758+
export function getDocumentTools(): Tool[] {
759+
return [
760+
{
761+
name: 'get_document_metadata',
762+
description: "Fetch a document's metadata",
763+
inputSchema: {
764+
type: 'object',
765+
properties: {
766+
document_id: {
767+
type: 'string',
768+
description: 'The document ID'
769+
}
770+
},
771+
required: ['document_id']
772+
}
773+
},
774+
{
775+
name: 'get_document_content',
776+
description: 'Fetch a document content (PDF)',
777+
inputSchema: {
778+
type: 'object',
779+
properties: {
780+
document_id: {
781+
type: 'string',
782+
description: 'The document ID'
783+
}
784+
},
785+
required: ['document_id']
786+
}
787+
}
788+
];
789+
}
790+
758791
export function getAllTools(): Tool[] {
759792
return [
760793
...getCompanyTools(),
761794
...getSearchTools(),
762795
...getOfficersTools(),
763796
...getFilingTools(),
764797
...getChargesTools(),
765-
...getPSCTools()
798+
...getPSCTools(),
799+
...getDocumentTools()
766800
];
767801
}

src/types/document.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { z } from 'zod';
2+
3+
export const DocumentMetadataSchema = z.object({
4+
document_id: z.string().min(1).describe('The document ID')
5+
});
6+
7+
export const DocumentContentSchema = z.object({
8+
document_id: z.string().min(1).describe('The document ID')
9+
});
10+
11+
export const DocumentMetadataResponseSchema = z.object({
12+
company_number: z.string().optional(),
13+
barcode: z.string().optional(),
14+
significant_date: z.string().optional(),
15+
significant_date_type: z.string().optional(),
16+
category: z.string().optional(),
17+
pages: z.number().optional(),
18+
filename: z.string().optional(),
19+
created_at: z.string().optional(),
20+
updated_at: z.string().optional(),
21+
etag: z.string().optional(),
22+
links: z
23+
.object({
24+
self: z.string().optional(),
25+
document: z.string().optional()
26+
})
27+
.optional(),
28+
resources: z
29+
.record(
30+
z.object({
31+
content_length: z.number().optional(),
32+
content_type: z.string().optional(),
33+
created_at: z.string().optional(),
34+
updated_at: z.string().optional()
35+
})
36+
)
37+
.optional()
38+
});
39+
40+
export type DocumentMetadata = z.infer<typeof DocumentMetadataSchema>;
41+
export type DocumentContent = z.infer<typeof DocumentContentSchema>;
42+
export type DocumentMetadataResponse = z.infer<typeof DocumentMetadataResponseSchema>;

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './charges.js';
66
export * from './psc.js';
77
export * from './psc-response.js';
88
export * from './search-response.js';
9+
export * from './document.js';

tests/document-api.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
import axios, { AxiosInstance } from 'axios';
3+
import { DocumentApiClient } from '../src/api/document-api';
4+
5+
vi.mock('axios');
6+
7+
describe('DocumentApiClient', () => {
8+
let apiClient: DocumentApiClient;
9+
let mockAxiosInstance: Partial<AxiosInstance>;
10+
11+
beforeEach(() => {
12+
mockAxiosInstance = {
13+
get: vi.fn(),
14+
interceptors: {
15+
response: {
16+
use: vi.fn()
17+
}
18+
}
19+
};
20+
21+
(axios.create as ReturnType<typeof vi.fn>).mockReturnValue(mockAxiosInstance as AxiosInstance);
22+
23+
apiClient = new DocumentApiClient({
24+
apiKey: 'test-api-key'
25+
});
26+
});
27+
28+
describe('getDocumentMetadata', () => {
29+
it('should fetch document metadata', async () => {
30+
const mockResponse = {
31+
data: {
32+
company_number: '12345678',
33+
barcode: 'ABC123',
34+
category: 'accounts',
35+
pages: 10,
36+
filename: 'document.pdf',
37+
links: {
38+
self: '/document/doc123',
39+
document: '/document/doc123/content'
40+
}
41+
}
42+
};
43+
44+
(mockAxiosInstance.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockResponse);
45+
46+
const result = await apiClient.getDocumentMetadata({ document_id: 'doc123' });
47+
48+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/document/doc123');
49+
expect(result).toEqual(mockResponse.data);
50+
});
51+
52+
it('should handle errors when fetching metadata', async () => {
53+
const error = new Error('API Error');
54+
(mockAxiosInstance.get as ReturnType<typeof vi.fn>).mockRejectedValue(error);
55+
56+
await expect(apiClient.getDocumentMetadata({ document_id: 'doc123' })).rejects.toThrow(
57+
'API Error'
58+
);
59+
});
60+
});
61+
62+
describe('getDocumentContent', () => {
63+
it('should fetch document content as buffer', async () => {
64+
const mockPdfData = new ArrayBuffer(1024);
65+
const mockResponse = {
66+
data: mockPdfData
67+
};
68+
69+
(mockAxiosInstance.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockResponse);
70+
71+
const result = await apiClient.getDocumentContent({ document_id: 'doc123' });
72+
73+
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/document/doc123/content', {
74+
responseType: 'arraybuffer',
75+
headers: {
76+
Accept: 'application/pdf'
77+
}
78+
});
79+
expect(result).toBeInstanceOf(Buffer);
80+
expect(result.length).toBe(1024);
81+
});
82+
83+
it('should handle errors when fetching content', async () => {
84+
const error = new Error('Failed to fetch document');
85+
(mockAxiosInstance.get as ReturnType<typeof vi.fn>).mockRejectedValue(error);
86+
87+
await expect(apiClient.getDocumentContent({ document_id: 'doc123' })).rejects.toThrow(
88+
'Failed to fetch document'
89+
);
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)