Publish @classroomio/mcp package
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import type {
|
||||
TCourseImportDraftCreate,
|
||||
TCourseImportDraftPublish,
|
||||
TCourseImportDraftUpdate
|
||||
} from '@cio/utils/validation/course-import';
|
||||
|
||||
import type { McpServerConfig } from './config.js';
|
||||
|
||||
type ApiSuccess<T> = {
|
||||
success: true;
|
||||
data: T;
|
||||
};
|
||||
|
||||
type ApiFailure = {
|
||||
success?: false;
|
||||
error?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
field?: string;
|
||||
};
|
||||
|
||||
export class ClassroomIoApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public status: number,
|
||||
public code?: string,
|
||||
public field?: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ClassroomIoApiError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ClassroomIoApiClient {
|
||||
constructor(private readonly config: McpServerConfig) {}
|
||||
|
||||
async createCourseDraft(payload: TCourseImportDraftCreate) {
|
||||
return this.request('/organization/course-import/drafts', {
|
||||
method: 'POST',
|
||||
body: payload
|
||||
});
|
||||
}
|
||||
|
||||
async getCourseDraft(draftId: string) {
|
||||
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
async updateCourseDraft(draftId: string, payload: TCourseImportDraftUpdate) {
|
||||
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
||||
method: 'PUT',
|
||||
body: payload
|
||||
});
|
||||
}
|
||||
|
||||
async publishCourseDraft(draftId: string, payload: TCourseImportDraftPublish) {
|
||||
return this.request(`/organization/course-import/drafts/${draftId}/publish`, {
|
||||
method: 'POST',
|
||||
body: payload
|
||||
});
|
||||
}
|
||||
|
||||
private async request<TResponse>(
|
||||
path: string,
|
||||
options: {
|
||||
method: 'GET' | 'POST' | 'PUT';
|
||||
body?: unknown;
|
||||
}
|
||||
): Promise<TResponse> {
|
||||
const response = await fetch(new URL(path, this.config.CLASSROOMIO_API_URL), {
|
||||
method: options.method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.config.CLASSROOMIO_API_KEY}`,
|
||||
'content-type': 'application/json',
|
||||
'user-agent': this.config.CLASSROOMIO_USER_AGENT
|
||||
},
|
||||
body: options.body ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
|
||||
const json = (await response.json().catch(() => null)) as ApiSuccess<TResponse> | ApiFailure | null;
|
||||
|
||||
if (!response.ok) {
|
||||
const errorPayload = json as ApiFailure | null;
|
||||
throw new ClassroomIoApiError(
|
||||
errorPayload?.error ?? errorPayload?.message ?? `ClassroomIO request failed with status ${response.status}`,
|
||||
response.status,
|
||||
errorPayload?.code,
|
||||
errorPayload?.field
|
||||
);
|
||||
}
|
||||
|
||||
if (!json || typeof json !== 'object' || !('success' in json) || !json.success) {
|
||||
throw new ClassroomIoApiError('ClassroomIO returned an invalid response payload', response.status);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZConfig = z.object({
|
||||
CLASSROOMIO_API_URL: z.string().url(),
|
||||
CLASSROOMIO_API_KEY: z.string().min(1),
|
||||
CLASSROOMIO_USER_AGENT: z.string().min(1).default('classroomio-mcp/0.0.1')
|
||||
});
|
||||
|
||||
export type McpServerConfig = z.infer<typeof ZConfig>;
|
||||
|
||||
export function getConfig(env: NodeJS.ProcessEnv = process.env): McpServerConfig {
|
||||
return ZConfig.parse(env);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
import { ClassroomIoApiClient, ClassroomIoApiError } from './api-client.js';
|
||||
import { getConfig } from './config.js';
|
||||
import { registerCourseDraftTools } from './tools/course-drafts.js';
|
||||
|
||||
async function main() {
|
||||
const config = getConfig();
|
||||
const apiClient = new ClassroomIoApiClient(config);
|
||||
const server = new McpServer({
|
||||
name: 'classroomio-course-authoring',
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
registerCourseDraftTools(server, apiClient);
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
process.stdin.on('close', () => {
|
||||
void server.close();
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
if (error instanceof ClassroomIoApiError) {
|
||||
console.error(
|
||||
JSON.stringify({
|
||||
error: error.message,
|
||||
status: error.status,
|
||||
code: error.code,
|
||||
field: error.field
|
||||
})
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
ZCourseImportDraftCreate,
|
||||
ZCourseImportDraftGetParam,
|
||||
ZCourseImportDraftPublish,
|
||||
ZCourseImportDraftUpdate
|
||||
} from '@cio/utils/validation/course-import';
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import type { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
||||
|
||||
import type { ClassroomIoApiClient } from '../api-client.js';
|
||||
|
||||
export const ZUpdateCourseDraftToolInput = ZCourseImportDraftUpdate.extend({
|
||||
draftId: ZCourseImportDraftGetParam.shape.draftId
|
||||
});
|
||||
|
||||
export const ZPublishCourseDraftToolInput = ZCourseImportDraftPublish.extend({
|
||||
draftId: ZCourseImportDraftGetParam.shape.draftId
|
||||
});
|
||||
|
||||
const createCourseDraftShape = ZCourseImportDraftCreate.shape as unknown as ZodRawShapeCompat;
|
||||
const getCourseDraftShape = ZCourseImportDraftGetParam.shape as unknown as ZodRawShapeCompat;
|
||||
const updateCourseDraftShape = ZUpdateCourseDraftToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const publishCourseDraftShape = ZPublishCourseDraftToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
|
||||
export function registerCourseDraftTools(server: McpServer, apiClient: ClassroomIoApiClient) {
|
||||
server.tool('create_course_draft', createCourseDraftShape, async (args) => {
|
||||
const payload = ZCourseImportDraftCreate.parse(args);
|
||||
const result = await apiClient.createCourseDraft(payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
|
||||
server.tool('get_course_draft', getCourseDraftShape, async (args) => {
|
||||
const { draftId } = ZCourseImportDraftGetParam.parse(args);
|
||||
const result = await apiClient.getCourseDraft(draftId);
|
||||
return jsonContent(result);
|
||||
});
|
||||
|
||||
server.tool('update_course_draft', updateCourseDraftShape, async (args) => {
|
||||
const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.updateCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
|
||||
server.tool('publish_course_draft', publishCourseDraftShape, async (args) => {
|
||||
const { draftId, ...payload } = ZPublishCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.publishCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
}
|
||||
|
||||
function jsonContent(data: unknown) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: JSON.stringify(data)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user