Skip to main content
Glama
scvcoder

korean-privacy-law-mcp

by scvcoder

get_article_change_history

Track amendment history of specific articles in Korean laws, including PIPA, by specifying law ID and optional date range to see changes over time.

Instructions

조문별 변경 이력 (법제처 lawSearch · target=lsJoHstInf). 특정 법령(또는 특정 조문)의 시점별 개정 추적. fromRegDt/toRegDt 날짜 범위 필수 (미지정 시 최근 10년 자동). PIPA 같은 자주 개정되는 법령의 조문 단위 히스토리 추적. 다음: get_historical_law(mst)로 그 시점 본문, compare_old_new로 신구 비교.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
lawIdYes법령ID (search_law의 lawId 필드 — 법령일련번호 mst와 다름)
joNo조문번호 — '제15조' 또는 '15' 또는 6자리 코드 '001500' 모두 수용. 미지정 시 전체 조문.
fromRegDtNo조회 시작일 YYYYMMDD (미지정 시 자동 — 10년 전부터)
toRegDtNo조회 종료일 YYYYMMDD (미지정 시 자동 — 오늘)

Implementation Reference

  • The tool definition and handler for 'get_article_change_history'. Defines the input schema, fetches article change history from the lawSearch API (target=lsJoHstInf), parses XML results, builds formatted text output, and returns suggestions for next steps (get_historical_law, compare_old_new).
    export const getArticleChangeHistory: Tool<typeof inputSchema> = {
      name: "get_article_change_history",
      description:
        "조문별 변경 이력 (법제처 lawSearch · target=lsJoHstInf). 특정 법령(또는 특정 조문)의 시점별 개정 추적. " +
        "fromRegDt/toRegDt 날짜 범위 필수 (미지정 시 최근 10년 자동). " +
        "PIPA 같은 자주 개정되는 법령의 조문 단위 히스토리 추적. " +
        "다음: get_historical_law(mst)로 그 시점 본문, compare_old_new로 신구 비교.",
      inputSchema,
    
      async handler(args, client) {
        try {
          const { from, to } = defaultDateRange();
          const fromRegDt = args.fromRegDt ?? from;
          const toRegDt = args.toRegDt ?? to;
    
          const extraParams: Record<string, string> = {
            ID: args.lawId,
            fromRegDt,
            toRegDt,
            display: "100",
          };
          if (args.jo) extraParams.JO = normalizeJoCode(args.jo);
    
          const xml = await client.fetchApi({
            endpoint: "lawSearch.do",
            target: "lsJoHstInf",
            extraParams,
          });
    
          const totalCnt = parseInt(extractTag(xml, "totalCnt"), 10) || 0;
          if (totalCnt === 0) {
            const joNote = args.jo ? ` · jo=${normalizeJoCode(args.jo)}` : "";
            return notFoundResponse(
              `조문 변경 이력 없음 (lawId=${args.lawId}${joNote}, ${fromRegDt}~${toRegDt})`,
              [
                `get_law_history(lawName="...") — 법령 자체의 연혁 목록`,
                `날짜 범위 확장: fromRegDt를 더 이른 날짜로 (예: 19900101)`,
              ]
            );
          }
    
          const lawBlocks = extractTagAll(xml, "law");
          const entries: LawHistoryEntry[] = [];
          for (const block of lawBlocks) {
            const infoXml = extractTag(block, "법령정보");
            const articlesXml = extractTag(block, "조문정보");
            if (!infoXml) continue;
    
            const joBlocks = extractTagAll(articlesXml, "jo");
            const items: JoHistoryItem[] = joBlocks.map((j) => ({
              조문번호: extractTag(j, "조문번호"),
              변경사유: extractTag(j, "변경사유"),
              조문개정일: extractTag(j, "조문개정일"),
              조문시행일: extractTag(j, "조문시행일"),
            }));
    
            entries.push({
              법령일련번호: extractTag(infoXml, "법령일련번호"),
              법령명한글: extractTag(infoXml, "법령명한글"),
              공포일자: extractTag(infoXml, "공포일자"),
              공포번호: extractTag(infoXml, "공포번호"),
              제개정구분명: extractTag(infoXml, "제개정구분명"),
              시행일자: extractTag(infoXml, "시행일자"),
              조문이력: items,
            });
          }
    
          // 시행일자 desc 정렬
          entries.sort((a, b) => b.시행일자.localeCompare(a.시행일자));
    
          const lawName = entries[0]?.법령명한글 ?? "(법령명 없음)";
          const joLabel = args.jo
            ? ` · ${denormalizeJoCode(normalizeJoCode(args.jo))}`
            : "";
          let text = `=== ${lawName} 조문 변경 이력${joLabel} ===\n`;
          text += `총 ${entries.length}개 시점 (${fromRegDt} ~ ${toRegDt})\n\n`;
    
          for (const entry of entries) {
            text += `[mst=${entry.법령일련번호}] ${entry.시행일자} 시행 · ${entry.제개정구분명}`;
            if (entry.공포번호) text += ` (공포 ${entry.공포일자}, 제${entry.공포번호}호)`;
            text += "\n";
            if (entry.조문이력.length > 0) {
              for (const j of entry.조문이력.slice(0, 10)) {
                text += `  ${denormalizeJoCode(j.조문번호)}: ${j.변경사유}`;
                if (j.조문시행일 && j.조문시행일 !== entry.시행일자) {
                  text += ` (조문 시행 ${j.조문시행일})`;
                }
                text += "\n";
              }
              if (entry.조문이력.length > 10) {
                text += `  ⋯ ${entry.조문이력.length - 10}개 조문 더 ⋯\n`;
              }
            }
            text += "\n";
          }
    
          // 첫 시점 mst로 본문 조회 추천
          const recent = entries[0];
          if (recent) {
            text = appendSuggestions(text, [
              {
                tool: "get_historical_law",
                args: { mst: recent.법령일련번호 },
                reason: `${recent.시행일자} 시점 본문`,
              },
              {
                tool: "compare_old_new",
                args: { mst: recent.법령일련번호 },
                reason: "직전 시점과 신구법 비교",
              },
            ]);
          }
          text += `\n📎 출처: ${lawName} 조문 변경 이력 (법제처 DB)`;
    
          return { content: [{ type: "text", text }] };
        } catch (err) {
          return formatToolError(err, "get_article_change_history");
        }
      },
  • Input schema (Zod) for the tool: lawId (required), jo (optional article number), fromRegDt/toRegDt (optional date range in YYYYMMDD).
    const inputSchema = z.object({
      lawId: z
        .string()
        .min(1)
        .describe("법령ID (search_law의 lawId 필드 — 법령일련번호 mst와 다름)"),
      jo: z
        .string()
        .optional()
        .describe(
          "조문번호 — '제15조' 또는 '15' 또는 6자리 코드 '001500' 모두 수용. 미지정 시 전체 조문."
        ),
      fromRegDt: z
        .string()
        .regex(/^\d{8}$/, "YYYYMMDD 형식")
        .optional()
        .describe("조회 시작일 YYYYMMDD (미지정 시 자동 — 10년 전부터)"),
      toRegDt: z
        .string()
        .regex(/^\d{8}$/, "YYYYMMDD 형식")
        .optional()
        .describe("조회 종료일 YYYYMMDD (미지정 시 자동 — 오늘)"),
    });
  • TypeScript interfaces JoHistoryItem and LawHistoryEntry used to structure parsed data.
    interface JoHistoryItem {
      조문번호: string;
      변경사유: string;
      조문개정일: string;
      조문시행일: string;
    }
    
    interface LawHistoryEntry {
      법령일련번호: string;
      법령명한글: string;
      공포일자: string;
      공포번호: string;
      제개정구분명: string;
      시행일자: string;
      조문이력: JoHistoryItem[];
    }
  • Helper functions normalizeJoCode (converts '제15조'/'15'/'001500' to 6-digit code) and denormalizeJoCode (converts back to '제15조'/'제24조의2' format).
    function normalizeJoCode(input: string): string {
      const cleaned = input.trim();
      // 이미 6자리 숫자
      if (/^\d{6}$/.test(cleaned)) return cleaned;
      // "제N조" 또는 "제N조의M"
      const m = cleaned.match(/^제?(\d+)조?(?:의(\d+))?$/);
      if (m && m[1]) {
        const main = m[1].padStart(4, "0");
        const branch = (m[2] ?? "0").padStart(2, "0");
        return main + branch;
      }
      // 그 외는 그대로 (API에 그대로 전달)
      return cleaned;
    }
    
    /** "001500" → "제15조", "002402" → "제24조의2" */
    function denormalizeJoCode(code: string): string {
      if (!/^\d{6}$/.test(code)) return code;
      const main = parseInt(code.substring(0, 4), 10);
      const branch = parseInt(code.substring(4, 6), 10);
      return branch === 0 ? `제${main}조` : `제${main}조의${branch}`;
    }
  • Import of getArticleChangeHistory from its implementation file.
    import { getArticleChangeHistory } from "./primitives/get-article-change-history.js";
  • Registration of getArticleChangeHistory in the ALL_TOOLS array, making it available to the MCP server.
    getArticleChangeHistory,
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description discloses default date range behavior and that the tool provides article-level history. It implies read-only operation. However, it does not explicitly state non-destructive nature or any rate limits, but adds useful behavior beyond raw schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, consisting of a few sentences in Korean. It front-loads the purpose and includes a use case and sibling references. It could be slightly shorter, but overall efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 4 parameters, no output schema, and many sibling tools, the description adequately explains when to use this tool and what it does. It provides context for defaults and references follow-up tools, but lacks details on return format (though no output schema).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so baseline is 3. The description adds minor value by restating default behavior for dates and the 'jo' parameter's flexibility, but does not provide significant additional meaning beyond what the schema already offers.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states '조문별 변경 이력' (article change history) and specifies tracking amendments of laws/articles over time. It distinguishes from siblings by mentioning 'get_historical_law(mst)' and 'compare_old_new' as next steps.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains that date range is required but defaults to recent 10 years if not specified, and provides a use case for frequently amended laws like PIPA. It references sibling tools for further actions, though it does not explicitly exclude when not to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/scvcoder/korean-privacy-law-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server