npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

accoding-mcp-server-new

v0.1.6

Published

MCP Server for Accoding API-test

Downloads

126

Readme

Accoding MCP Server

为北京航空航天大学 Accoding 在线编程平台开发的 Model Context Protocol (MCP) 服务器,提供完整的题目搜索、提交管理、用户信息查询等功能,支持 AI 智能体通过标准化接口与平台交互。

✨ 特性

  • 完整的 MCP 协议支持:基于 @modelcontextprotocol/sdk 实现标准 MCP 服务器
  • 双传输模式:支持 stdio(本地开发)和 HTTP(服务器部署)两种传输方式
  • 会话隔离:HTTP 模式下每个连接独立的会话和服务实例
  • 分层架构:清晰的 API → Service → Tool 分层设计,易于扩展和维护
  • 类型安全:使用 TypeScript 和 Zod 进行完整的类型检查和参数验证
  • 智能标签转换:自动将用户友好的标签名称转换为 API 所需的标签 ID

📁 项目结构

accoding-mcp-server/
├── src/
│   ├── common/                    # 通用基础层
│   │   ├── constants.ts          # 常量定义(语言、分类、标签等)
│   │   └── registry-base.ts      # 注册基类(认证/非认证组件管理)
│   ├── accoding/                  # Accoding 服务层
│   │   ├── api/                  # RESTful API 调用层
│   │   │   ├── problem-api.ts    # 题目相关 API
│   │   │   ├── user-api.ts       # 用户相关 API
│   │   │   ├── contest-api.ts    # 比赛相关 API
│   │   │   ├── class-api.ts      # 班级相关 API(题目/考试列表、班级信息等)
│   │   │   └── submission-api.ts # 提交相关 API
│   │   ├── accoding-base-service.ts     # 服务接口定义
│   │   ├── accoding-service-factory.ts  # 服务工厂类
│   │   └── accoding-service-impl.ts     # 服务实现(业务逻辑)
│   ├── mcp/                      # MCP 工具和资源层
│   │   ├── tools/                # MCP 工具注册
│   │   │   ├── tool-registry.ts         # 工具注册基类
│   │   │   ├── problem-tools.ts         # 题目工具
│   │   │   ├── user-tools.ts            # 用户与班级相关工具(含班级题目/班级考试列表)
│   │   │   ├── platform-tools.ts        # 平台静态信息工具(无需登录)
│   │   │   ├── class-tools.ts           # 班级信息工具(课程介绍等,无需登录)
│   │   │   ├── contest-tools.ts         # 比赛/考试工具
│   │   │   └── submission-tools.ts      # 提交工具
│   │   └── resources/            # MCP 资源注册
│   │       ├── resource-registry.ts      # 资源注册基类
│   │       ├── problem-resources.ts     # 题目资源
│   │       └── contest-resources.ts     # 比赛资源
│   ├── transport/                 # 传输层
│   │   ├── http-server.ts        # HTTP 服务器实现
│   │   └── mcp-server-factory.ts # MCP Server 创建工厂
│   ├── utils/
│   │   ├── logger.ts             # 日志工具(pino)
│   │   └── http-utils.ts         # HTTP 工具函数(CORS、cookie解析)
│   └── index.ts                   # 程序入口
├── package.json
├── tsconfig.json
├── ecosystem.config.cjs          # PM2 配置文件
└── README.md

🏗️ 架构设计

分层架构

项目采用清晰的分层架构,从下到上分为:

  1. API 层 (accoding/api/):封装 Accoding 平台的 RESTful API 调用
  2. 服务层 (accoding/):实现业务逻辑,处理数据转换和聚合
  3. 工具层 (mcp/tools/):注册 MCP 工具,定义 AI 可调用的接口
  4. 传输层 (transport/):处理 MCP 协议的传输(stdio/HTTP)

设计模式

  • 工厂模式AccodingServiceFactory 创建服务实例
  • 注册模式RegistryBase 统一管理工具和资源的注册,根据认证状态动态注册
  • 接口隔离AccodingBaseService 定义服务接口,AccodingServiceImpl 实现具体逻辑

认证机制

  • stdio 模式:通过 --session 参数传递 Accoding session cookie
  • HTTP 模式:通过 X-Accoding-Session header 或 Cookie header 传递
  • 会话隔离:每个 HTTP 连接都有独立的会话 ID 和服务实例

🚀 快速开始

安装依赖

npm install

编译项目

npm run build

运行服务器

stdio 模式(默认,用于本地开发)

node build/index.js --session "your-accoding-session-cookie"

HTTP 模式(用于服务器部署)

node build/index.js --mode http --port 3000

命令行参数

--session, -s <cookie>   Accoding平台会话Cookie(stdio模式使用)
--mode, -m <mode>        传输模式: stdio(默认)或 http
--port, -p <port>        HTTP模式端口号(默认: 3000)
--help, -h               显示帮助信息

💻 代码示例

API 层示例

API 层负责封装 HTTP 请求,处理参数构建和响应解析:

// src/accoding/api/problem-api.ts
const API_BASE_URL = "http://8.141.100.178:8000";

async function apiRequest(
    endpoint: string,
    options: RequestInit = {},
    session?: string
): Promise<any> {
    const url = `${API_BASE_URL}${endpoint}`;
    const headers: Record<string, string> = {
        "Content-Type": "application/json",
        ...(options.headers as Record<string, string>)
    };

    if (session) {
        headers["Authorization"] = session;
    }

    const response = await fetch(url, { ...options, headers });
    // ... 错误处理和响应解析
}

export async function searchProblems(
    page: number,
    limit: number,
    titleSearch?: string,
    tagSearch?: number[],
    session?: string
): Promise<any> {
    const params = new URLSearchParams();
    params.append("page", page.toString());
    params.append("limit", limit.toString());
    
    if (titleSearch) {
        params.append("titleSearch", titleSearch);
    }
    
    // tagSearch 是 int 数组,使用重复参数格式
    if (tagSearch && tagSearch.length > 0) {
        tagSearch.forEach((tagId) => {
            params.append("tagSearch", tagId.toString());
        });
    }
    
    const endpoint = `/api/problem/search?${params.toString()}`;
    const response = await apiRequest(endpoint, { method: "GET" }, session);
    
    if (response.status !== 200) {
        throw new Error(`搜索题目失败: ${response.msg || "未知错误"}`);
    }
    
    return response.data || [];
}

服务层示例

服务层实现业务逻辑,如标签名称到 ID 的转换、自动分页等:

// src/accoding/accoding-service-impl.ts
export class AccodingServiceImpl implements AccodingBaseService {
    private session?: string;

    async searchProblems(
        titleSearch?: string,
        idSearch?: string,
        authorSearch?: string,
        tagNames?: string[]
    ): Promise<any> {
        const limit = 10; // 固定每页10道题目
        
        // 标签名称转 ID
        let tagIds: number[] | undefined;
        if (tagNames && tagNames.length > 0) {
            const allTags = await problemApi.getAllTags(this.session);
            const tagMap = new Map<string, number>();
            allTags.forEach((tag: any) => {
                tagMap.set(tag.name, tag.id);
            });
            
            tagIds = tagNames
                .map((name) => tagMap.get(name))
                .filter((id): id is number => id !== undefined);
        }
        
        // 获取总页数
        const totalPages = await problemApi.getProblemSearchPageNum(
            limit, titleSearch, idSearch, authorSearch, tagIds, this.session
        );
        
        // 自动获取所有页面(最多20页)
        const MAX_PAGES = 20;
        const pagesToFetch = Math.min(totalPages, MAX_PAGES);
        const allProblems: any[] = [];
        
        for (let page = 1; page <= pagesToFetch; page++) {
            const problems = await problemApi.searchProblems(
                page, limit, titleSearch, idSearch, authorSearch, tagIds, this.session
            );
            allProblems.push(...problems);
        }
        
        // 添加链接和状态说明
        const problemsWithLinks = allProblems.map((problem: any) => ({
            ...problem,
            link: `http://8.141.100.178:8000/problem/${problem.id}`,
            statusText: statusMap[problem.status],
            difficultyText: `${problem.difficulty}颗星`
        }));
        
        return {
            problems: problemsWithLinks,
            totalPages,
            fetchedPages: pagesToFetch,
            isTruncated: totalPages > MAX_PAGES
        };
    }
}

工具注册示例

工具层使用 Zod 定义参数模式,注册 MCP 工具:

// src/mcp/tools/problem-tools.ts
import { z } from "zod";
import { ToolRegistry } from "./tool-registry.js";

export class ProblemToolRegistry extends ToolRegistry {
    protected registerAuthenticated(): void {
        this.registerSearchProblems();
        this.registerGetProblemInfo();
    }

    private registerSearchProblems(): void {
        this.server.tool(
            "search_problems",
            "搜索题目列表,支持按标题、ID、作者、标签等条件搜索。",
            {
                titleSearch: z.string().optional()
                    .describe("题目标题模糊搜索(可选)"),
                idSearch: z.string().optional()
                    .describe("题目ID精确搜索(可选)"),
                authorSearch: z.string().optional()
                    .describe("创建者的nickname模糊搜索(可选)"),
                tagNames: z.array(z.string()).optional()
                    .describe("按标签搜索题目,传入标签名称数组。例如:['判断', '二叉树']")
            },
            async ({ titleSearch, idSearch, authorSearch, tagNames }) => {
                try {
                    const result = await this.accodingService.searchProblems(
                        titleSearch, idSearch, authorSearch, tagNames
                    );
                    
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify(result, null, 2)
                        }]
                    };
                } catch (error: any) {
                    return {
                        content: [{
                            type: "text",
                            text: JSON.stringify({
                                error: "Failed to search problems",
                                message: error.message
                            })
                        }],
                        isError: true
                    };
                }
            }
        );
    }
}

注册基类示例

使用注册基类管理认证/非认证组件的动态注册:

// src/common/registry-base.ts
export abstract class RegistryBase {
    constructor(
        protected server: McpServer,
        protected accodingService: AccodingBaseService
    ) {}

    protected isAuthenticated(): boolean {
        return this.accodingService.isAuthenticated();
    }

    public register(): void {
        this.registerCommon(); // 注册通用组件
        
        if (this.isAuthenticated()) {
            this.registerAuthenticated(); // 注册需要认证的组件
        }
    }

    protected registerCommon(): void {}
    protected registerAuthenticated(): void {}
}

📦 已实现功能

当前 MCP 工具共 19 个。注册逻辑见各 src/mcp/tools/*-tools.tsRegistryBase 会先注册「无需登录」工具,若当前会话带有效 Authorization(已登录),再注册「需要登录」工具。

以下为每个工具的功能说明(按调用场景组织)。

无需登录(2)

get_platform_info
不请求远端接口。返回 Accoding 平台静态介绍:开发团队名单、推荐教材与购书链接、联系邮箱等,供助手回答「关于平台 / 教材 / 联系方式」类问题。

get_class_info
根据 classId 获取单个班级的课程介绍面板数据。优先请求管理端接口,失败则回退到公开接口;不传 Authorization。返回中通常包含课程名称、所属类别、课程简介等(具体字段以接口 data 为准),并附带 raw 便于排查字段差异。


需要登录(17)

题目(problem-tools.ts

search_problems
支持按题目标题模糊检索、按题目 ID精确检索、按创建者昵称检索、按标签名称数组检索(内部会把标签名转为标签 ID)。会先取总页数,再按页拉取并合并结果(最多 20 页,每页 10 道题)。返回每道题含题目链接、作答状态说明(已通过 / 未通过 / 未做)、难度星级文案等聚合信息。

get_problem_info
根据题目 ID 获取单题详情:题目内容、时间/内存限制(含格式化说明)、提交与通过统计、支持的语言列表;并计算通过率、正确率等可读指标及题目直达链接。

用户与班级(user-tools.ts

get_user_info
获取当前登录用户档案:昵称、邮箱、学校、学号、专业等(以接口返回为准)。

get_class_list
支持按班级名称课程名称筛选;两参数都省略时相当于拉取班级列表。返回每条班级含 statusstatusText1 表示课程进行中,0 表示已结束;并返回 statusDescription 供模型理解枚举含义。

get_my_class_list
获取当前用户已加入的班级列表,字段含义与 get_class_list 类似,同样附带 statusTextstatusDescription

get_class_problem_list
根据 classId 获取该班级下的题目列表(默认按后端分页一次拉满)。返回每道题含题目 ID、标题、难度、标签等;并增加题目访问链接、题目 statusstatusText(如可用/禁用)及解析后的 tagNames

get_class_contest_list
根据 classId 获取该班级下的考试/比赛列表(多页合并策略与比赛列表工具一致)。返回每场考试含 examId、名称、时间、题目数等;并增加考试首页 link、考试进度字段 statstatText(未开始 / 进行中 / 已结束)。

get_user_submission_stats
获取当前用户在平台上的提交结果汇总:各评测结果(如 AC、CE、WA 等)对应的数量,并附带结果缩写的人类可读说明。

get_user_weekly_submissions
获取最近一周(按接口约定)每天的提交分布;返回中会把原始数组格式化为「星期几 + 各状态数量」等便于阅读的结构,并保留 rawData 供核对。

比赛 / 考试(contest-tools.ts

list_contests
支持按比赛名称、按所在班级名称(对应后端 AuthorSearch)筛选。先取总页数,再分页拉取并合并(最多 20 页,每页条数可配置上限)。返回每场含 examId、时间、状态等,并含状态文案与考试首页链接。

get_contest_exam_info
根据 examId 获取单场考试/比赛的基础信息:名称、开始/结束时间、描述等;并附带考试首页链接与原始 data,便于计算剩余时间等。

get_contest_exam_problems_and_status
并行获取该考试的题目列表当前用户对每道题的提交状态列表,按题目顺序合并为一条记录:每题含题目内容与统计、语言限制等,以及 userStatus / userStatusText(如未提交、已通过、提交但错误);并含题目直达链接。

get_contest_exam_rank_list
根据 examId 获取该考试的排名榜(顺序、学号、昵称、得分、每题罚时等,以接口为准)。

get_contest_exam_submissions
获取当前登录用户在该考试下的个人提交记录(不是全员排行榜里的所有人提交)。内部固定 pageSize=20,从第 1 页依次请求到最后一页并拼接为完整列表。可选 filter:扁平对象,键名与前端 SubmissionListsearchFilter 一致(如 submissionIdproblemIdnicknameresult),会作为 Query 参数与 page / pageSize 一并传递。返回含 totalPagesfetchedPages、每条记录的跳转路径、题目链接、评测结果文案,以及 note 强调数据范围仅为当前用户。

提交(submission-tools.ts

get_problem_submissions
根据题目 ID 获取该题的全部提交记录(可先取总页数再逐页合并)。支持按评测结果、提交 ID 等过滤(与工具参数一致)。返回每条含结果的人类可读说明。

get_submission_detail
根据提交记录 ID 同时拉取评测详情提交代码;含时间/内存格式化和题目链接等。

submit_code
向指定题目提交代码:传入题目 ID、语言名称(内部映射为语言 ID)、代码正文;可选提交时间与题目时间(缺省为当前时间)。返回提交结果摘要。


说明:源码中仍存在 get_contest_problems 的注册函数,但默认未在 registerCommon / registerAuthenticated 中启用,因此不计入上述 19 个对外工具。

🔧 开发指南

添加新工具

  1. 在 API 层添加 API 调用函数 (src/accoding/api/)
// src/accoding/api/xxx-api.ts
export async function getXxx(params: any, session?: string): Promise<any> {
    const response = await apiRequest("/api/xxx", { method: "GET" }, session);
    if (response.status !== 200) {
        throw new Error(`获取失败: ${response.msg}`);
    }
    return response.data;
}
  1. 在服务接口中添加方法 (src/accoding/accoding-base-service.ts)
export interface AccodingBaseService {
    getXxx(params: any): Promise<any>;
}
  1. 在服务实现中实现方法 (src/accoding/accoding-service-impl.ts)
async getXxx(params: any): Promise<any> {
    return await xxxApi.getXxx(params, this.session);
}
  1. 在工具注册器中注册工具 (src/mcp/tools/xxx-tools.ts)
export class XxxToolRegistry extends ToolRegistry {
    protected registerAuthenticated(): void {
        this.server.tool(
            "get_xxx",
            "工具描述",
            {
                param: z.string().describe("参数说明")
            },
            async ({ param }) => {
                const result = await this.accodingService.getXxx(param);
                return {
                    content: [{
                        type: "text",
                        text: JSON.stringify(result, null, 2)
                    }]
                };
            }
        );
    }
}
  1. 在 MCP Server 工厂中注册工具 (src/transport/mcp-server-factory.ts)
import { registerXxxTools } from "../mcp/tools/xxx-tools.js";

export function createMCPServer(accodingService: AccodingBaseService): McpServer {
    // ...
    registerXxxTools(server, accodingService);
    // ...
}

开发模式

使用 tsc-watch 进行开发,代码变更自动重新编译:

npm run dev

代码格式化

npm run format

🚢 部署说明

PM2 部署

项目已配置 PM2 配置文件 ecosystem.config.cjs,可直接使用 PM2 管理进程:

# 启动服务
pm2 start ecosystem.config.cjs

# 查看状态
pm2 status

# 查看日志
pm2 logs accoding-mcp
pm2 logs accoding-mcp --lines 50

# 重启应用
pm2 restart accoding-mcp

# 停止应用
pm2 stop accoding-mcp

# 删除应用
pm2 delete accoding-mcp

# 监控
pm2 monit

# 重新加载(代码更新后,零停机重启)
pm2 reload accoding-mcp

开机自启动

# 1. 保存当前 PM2 配置
pm2 save

# 2. 添加到 crontab
(crontab -l 2>/dev/null; echo "@reboot cd /path/to/accoding_mcp_server && pm2 resurrect") | crontab -

# 3. 验证添加成功
crontab -l

HTTP 模式使用示例

初始化连接

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "X-Accoding-Session: your-session-cookie" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "initialize",
    "params": {}
  }'

响应会包含 mcp-session-id header,后续请求需要使用此 ID。

调用工具

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: <从初始化响应中获取的session-id>" \
  -H "X-Accoding-Session: your-session-cookie" \
  -d '{
    "jsonrpc": "2.0",
    "id": "2",
    "method": "tools/call",
    "params": {
      "name": "search_problems",
      "arguments": {
        "tagNames": ["判断"]
      }
    }
  }'

会话隔离

  • 每个 HTTP 连接都有独立的会话 ID
  • 每个会话都有独立的 AccodingService 实例
  • 不同客户端的 session cookie 互不影响
  • 连接关闭后自动清理会话资源

📝 环境变量

  • ACCODING_SESSION: Accoding 会话 Cookie(stdio 模式使用,HTTP 模式从请求头获取)

注意:本项目为北京航空航天大学 Accoding 平台的 MCP 服务器实现,仅供学习和研究使用。