洪 民憙 (Hong Minhee)
@hongminhee@hackers.pub · 1004 following · 710 followers
Hi, I'm who's behind Fedify, Hollo, BotKit, and this website, Hackers' Pub! My main account is at
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify, Hollo, BotKit, 그리고 보고 계신 이 사이트 Hackers' Pub을 만들고 있습니다. 제 메인 계정은:
@hongminhee洪 民憙 (Hong Minhee)
.
Fedify、Hollo、BotKit、そしてこのサイト、Hackers' Pubを作っています。私のメインアカウントは「
@hongminhee洪 民憙 (Hong Minhee)
」に。
Website
- hongminhee.org
GitHub
- @dahlia
Hollo
- @hongminhee@hollo.social
DEV
- @hongminhee
velog
- @hongminhee
Qiita
- @hongminhee
Zenn
- @hongminhee
Matrix
- @hongminhee:matrix.org
X
- @hongminhee
클로드 코드 쓰면서 처리가 가장 오래걸리는 프롬프트: /compact
데이터베이스 공부 중 하나로 B+트리 C로 구현하고 있는데, 포인터 사용, 정렬, 재귀 호출, DFS, 이진 탐색까지 한 번에 연습하기 좋은, 학습에 아주 훌륭한 자료구조구만.
洪 民憙 (Hong Minhee) shared the below article:
Agent Skill도 Tool Use로 시작합니다.
자손킴 @jasonkim@hackers.pub
Agent Skill은 Anthropic이 2025년 10월에 발표한 기능이다. 발표 직후부터 폭발적인 반응을 얻어 커뮤니티에서 다양한 종류의 Skill이 만들어졌다. 2025년 12월 18일에 Anthropic은 Agent Skills를 독립적인 오픈 스탠다드로 발표했고 여러 서비스들이 Skill을 지원하고 있다.
이번 글에서는 Agent Skill이 Tool Use 위에서 어떻게 동작하는지 알아본다.
Agent Skill이란?
Agent Skill은 에이전트가 특정 작업을 더 정확하고 효율적으로 수행할 수 있도록 지시문(instructions), 스크립트(scripts), 리소스(resources) 등을 동적으로 불러올 수 있게 구성된 폴더다.
에이전트는 점점 더 많은 것을 할 수 있지만 실제 업무를 안정적으로 수행하려면 절차적 지식과 조직별 맥락이 필요하다. PDF 양식을 채우는 방법, 데이터베이스 마이그레이션을 안전하게 수행하는 순서, 브라우저 자동화의 베스트 프랙티스 같은 것들이다. 이런 지식을 매번 프롬프트에 모두 작성하면 컨텍스트를 낭비하게 되고 일관성도 떨어진다.
Agent Skill은 이러한 문제들을 해결하기 위해 작업에 필요한 지식을 재사용 가능한 단위로 패키징하고 필요할 때만 동적으로 로드한다.
효율적인 컨텍스트 관리 방법
Agent Skill은 점진적 공개(Progressive Disclosure) 패턴으로 컨텍스트를 효율적으로 관리한다. 점진적 공개는 다음과 같은 단계로 구성된다.
첫 번째 단계: 메타데이터 로드
에이전트가 시작할 때 모든 Skill의 name과 description만 로드한다. 이 메타데이터는 Claude가 각 Skill을 언제 사용해야 하는지 판단할 수 있을 만큼의 정보만 제공한다. 예를 들어 PDF Skill은 "PDF 파일에서 텍스트 추출, 폼 채우기, 문서 병합을 수행한다"는 설명만 시스템 프롬프트에 포함된다.
두 번째 단계: SKILL.md 전체 로드
Claude가 현재 작업에 해당 Skill이 관련 있다고 판단하면 전체 SKILL.md를 컨텍스트에 로드한다. 이 단계에서 상세한 지시문이 추가된다. 권장 크기는 5000 토큰 미만이다.
세 번째 단계 이상: 추가 파일 온디맨드 로드
Skill이 복잡해지면 모든 내용을 SKILL.md 하나에 담기 어려워진다. 이런 경우 references/, scripts/, assets/ 폴더에 추가 파일을 번들하고 SKILL.md에서 참조한다. Claude는 필요할 때만 이 파일들을 탐색하고 로드한다.
이 패턴의 장점은 "필요할 때만 필요한 만큼"이다. 모든 Skill의 전체 지시문을 처음부터 로드하면 컨텍스트가 금방 소진된다. 점진적 공개는 이 문제를 해결하면서도 에이전트가 적절한 시점에 적절한 Skill을 활성화할 수 있게 한다.
Agent Skill의 구조
일반적인 Skill의 구조는 다음과 같다.
skill-name/
├── SKILL.md # 필수: 메타데이터 + 지시문
├── scripts/ # 선택: 실행 가능한 코드
├── references/ # 선택: 추가 문서
└── assets/ # 선택: 템플릿, 리소스
SKILL.md만 필수이고 나머지는 모두 선택이다. 단순한 Skill은 SKILL.md 하나만으로 구성될 수 있고 복잡한 Skill은 여러 개의 스크립트와 참조 문서를 포함할 수 있다.
필수요소인 SKILL.md는 다음과 같은 포맷으로 구성된다.
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents.
license: Apache-2.0
compatibility: Designed for Claude Code
metadata:
author: example-org
version: "1.0"
allowed-tools: Bash(git:*) Read
---
# PDF Processing
## When to use this skill
Use this skill when the user needs to work with PDF files...
## How to extract text
1. Use pdfplumber for text extraction...
SKILL.md는 YAML frontmatter와 마크다운 본문으로 구성된다. YAML frontmatter에는 name과 description이 필수로 포함되어야 한다. name은 최대 64자의 소문자와 숫자 그리고 하이픈으로만 구성되며 하이픈으로 시작하거나 끝날 수 없다. description은 최대 1024자로 이 Skill이 무엇을 하는지 언제 사용해야 하는지 설명한다.
license, compatibility, metadata, allowed-tools는 선택 필드다. 각 선택 필드의 역할은 다음과 같다.
- license: 라이선스 이름 또는 번들된 라이선스 파일에 대한 참조를 명시한다.
- compatibility: 최대 500자. 환경 요구사항을 명시한다. 의도한 제품, 필요한 시스템 패키지, 네트워크 접근 필요 여부 등을 기술한다. 예를 들어 "Designed for Claude Code" 또는 "Requires git, docker, jq, and access to the internet" 같은 형태로 작성한다. 대부분의 Skill은 이 필드가 필요하지 않다.
- metadata: 임의의 키-값 쌍을 저장하는 맵이다. author, version 같은 추가 속성을 담는다.
- allowed-tools: 공백으로 구분된 사전 승인 도구 목록이다. 실험적 기능으로 에이전트 구현에 따라 지원 여부가 다를 수 있다.
YAML frontmatter 아래의 마크다운 본문이 실제 지시문이 된다. 이 지시문은 Skill이 활성화될 때 컨텍스트에 주입되어 에이전트의 행동을 안내한다.
첫번째 단계에서는 frontmatter의 name과 description만 사용된다. 이 정보로 에이전트는 언제 이 Skill을 활성화해야 하는지 판단한다. 두번째 단계에서 SKILL.md 전체가 로드되고 세번째 단계에서 마크다운 본문의 지시문에 따라 scripts/ 폴더의 코드를 실행하거나 references/ 폴더의 추가 문서를 참조한다.
dev-browser로 살펴보는 실제 Skill 동작
이제 Skill이 어떻게 동작하는지 실제 예시를 통해 살펴보자. 이 예시는 Use Claude Code with Chrome에 있는 사용 예시를 dev-browser Skill을 사용해 테스트하고 분석한 것이다.
사용한 프롬프트는 다음과 같다.
Go to code.claude.com/docs, click on the search box,
type "hooks", and tell me what results appear
이 요청은 tools 배열과 함께 전송되며 Skill도 tools 배열에 포함되어 있다. Claude API 요청 구조에 대해서는 이전 글을 참고한다.
{
"name": "Skill",
"description": "Execute a skill within the main conversation\n\n<skills_instructions>...",
"input_schema": {
"type": "object",
"properties": {
"skill": {
"type": "string",
"description": "The skill name. E.g., \"commit\", \"review-pr\", or \"pdf\""
},
"args": {
"type": "string",
"description": "Optional arguments for the skill"
}
},
"required": ["skill"]
}
}
Skill 도구의 description에는 <available_skills> 섹션이 포함되어 있어 사용 가능한 모든 Skill의 목록과 설명이 들어있다.
<available_skills>
<skill>
<n>dev-browser:dev-browser</n>
<description>
Browser automation with persistent page state. Use when users ask to
navigate websites, fill forms, take screenshots, extract web data,
test web apps, or automate browser workflows. Trigger phrases include
"go to [url]", "click on", "fill out the form", "take a screenshot"...
</description>
<location>plugin</location>
</skill>
</available_skills>
사용자의 요청 "Go to code.claude.com/docs, click on the search box..."가 dev-browser의 description에 있는 트리거 프레이즈 "go to [url]", "click on"과 매칭된다. 에이전트는 이 매칭을 발견하고 Skill 도구를 호출한다.
{
"type": "tool_use",
"id": "toolu_017StpNdwovc4Lm8tGfK9XnA",
"name": "Skill",
"input": {
"skill": "dev-browser:dev-browser",
"args": "Go to code.claude.com/docs, click on the search box, type \"hooks\", and tell me what results appear"
}
}
skill 필드에 plugin name을 포함한 qualified name(plugin-name:skill-name)이 사용되고 args에는 사용자의 원본 요청이 그대로 전달되었다.
Skill 도구의 tool_result로 SKILL.md 전체 내용이 반환된다.
Launching skill: dev-browser:dev-browser
Base directory for this skill: /Users/dev-test/.claude/plugins/cache/
dev-browser-marketplace/dev-browser/58c332a7c61a/skills/dev-browser
# Dev Browser Skill
Browser automation that maintains page state across script executions.
Write small, focused scripts to accomplish tasks incrementally...
## Setup
First, start the dev-browser server using the startup script:
```bash
./skills/dev-browser/server.sh &
```
## Writing Scripts
Execute scripts inline using heredocs:
```bash
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("homepage");
// Your automation code here
await client.disconnect();
EOF
```
ARGUMENTS: Go to code.claude.com/docs, click on the search box...
tool_result에는 SKILL.md에 있는 모든 지시문이 포함되어 있다. ARGUMENTS에는 원본 사용자 요청이 첨부되어 있어 에이전트가 참조할 수 있다.
이제 에이전트는 SKILL.md의 지시문을 읽고 순서대로 작업을 수행한다. 먼저 Setup 섹션에 따라 서버를 시작한다.
{
"type": "tool_use",
"name": "Bash",
"input": {
"command": "cd /Users/dev-test/.claude/plugins/cache/dev-browser-marketplace/dev-browser/58c332a7c61a/skills/dev-browser && ./server.sh &",
"description": "Start dev-browser server"
}
}
서버가 준비되면 SKILL.md의 인라인 코드 템플릿을 참고하여 브라우저 자동화 스크립트를 작성하고 실행한다.
{
"type": "tool_use",
"name": "Bash",
"input": {
"command": "cd /Users/dev-test/.claude/plugins/cache/dev-browser-marketplace/dev-browser/58c332a7c61a/skills/dev-browser && npx tsx <<'EOF'\nimport { connect, waitForPageLoad } from \"@/client.js\";\n\nconst client = await connect();\nconst page = await client.page(\"claude-docs\");\nawait page.setViewportSize({ width: 1280, height: 800 });\n\nawait page.goto(\"https://docs.anthropic.com/en/docs/claude-code\");\nawait waitForPageLoad(page);\n\nconsole.log(\"Current URL:\", page.url());\nconst snapshot = await client.getAISnapshot(\"claude-docs\");\nconsole.log(snapshot);\n\nawait client.disconnect();\nEOF",
"description": "Navigate to Claude Code docs"
}
}
에이전트는 SKILL.md의 코드 템플릿을 그대로 복사하지 않는다. 템플릿 구조(import, connect, heredoc 패턴)를 따르되 작업에 맞게 즉석에서 코드를 작성한다. page.goto() URL이나 selectSnapshotRef() ref ID 등을 동적으로 결정한다.
SKILL.md가 명시한 Workflow Loop 패턴에 따라 Write a script → Run it → Evaluate → Decide → Repeat 과정이 반복된다. 페이지 탐색 스크립트 실행 → ARIA 스냅샷 확인 → 검색 버튼 클릭 → 검색어 입력 → 결과 확인 순서로 진행된다.
전체 흐름을 정리하면 다음과 같다.
User: "Go to code.claude.com/docs... Use dev-browser"
│
▼
LLM: available_skills에서 매칭 발견
description에 "go to", "click on" 트리거 포함
│
▼
tool_use: Skill
skill: "dev-browser:dev-browser"
args: "Go to code.claude.com/docs..."
│
▼
tool_result: SKILL.md 전체 + Base directory + ARGUMENTS
│
▼
LLM: SKILL.md 지시문 해석
"First, start the dev-browser server"
│
▼
tool_use: Bash (./server.sh &) ──► tool_result: "Server ready"
│
▼
LLM: heredoc 템플릿 참고하여 스크립트 작성
page.goto(), getAISnapshot() 활용
│
▼
tool_use: Bash (npx tsx <<'EOF'...)
│
▼
tool_result: snapshot 출력 (ARIA 트리)
│
▼
(반복: 클릭, 입력, 스크린샷 등)
Skill 도구의 역할은 SKILL.md 파일 경로를 해석하고 전체 내용을 tool_result로 반환하는 것뿐이다. 실제 능력은 에이전트가 SKILL.md를 읽고 지시문에 따라 다른 도구들을 사용하면서 발현된다.
Agent Skill과 Subagent
Subagent와 Agent Skill은 서로 다른 문제를 해결한다.
Subagent는 컨텍스트 분리가 필요할 때 사용한다. 탐색이나 분석 과정이 메인 대화를 오염시키면 안 될 때 적합하다. 예를 들어 코드베이스 전체를 탐색해야 하는데 그 과정의 모든 파일 내용이 메인 컨텍스트에 쌓이면 금방 컨텍스트가 소진된다. Subagent는 독립적인 컨텍스트 윈도우에서 작업하고 결과만 반환한다. 또한 가벼운 모델(Haiku)로 빠르게 처리하거나 무거운 모델(Opus)로 깊이 분석하는 선택이 가능하다.
Agent Skill은 절차적 지식이 필요할 때 사용한다. PDF 폼 채우기나 브라우저 자동화처럼 "어떻게 해야 하는지"에 대한 베스트 프랙티스가 있는 작업에 적합하다. Skill은 현재 컨텍스트를 공유하면서 지시문만 추가로 주입한다. 별도의 메시지 루프를 만들지 않는다.
Agent Skill과 MCP
MCP와 Agent Skill도 역할이 다르다.
MCP는 외부 시스템과의 연동이 필요할 때 사용한다. 브라우저, 데이터베이스, 외부 API처럼 에이전트 내부에서 직접 실행하기 어려운 도구가 필요할 때 적합하다. MCP 서버는 외부 프로세스에서 실행되고 프로토콜을 통해 통신한다. 같은 도구를 여러 에이전트에서 공유할 수도 있다.
Agent Skill은 도구 사용 방법을 가르칠 때 사용한다. MCP가 "어떤 도구가 사용 가능한지"를 알려준다면 Skill은 "그 도구를 어떻게 효과적으로 사용하는지"를 가르친다. 실제로 mcp-builder라는 Skill은 MCP 서버를 더 잘 만들기 위한 지식을 제공한다. Skill이 MCP를 대체하는 것이 아니라 보완하는 관계다.
마무리
지금까지 Agent Skill이 Tool Use 위에서 어떻게 동작하는지 알아보았다.
Skill 도구가 tools 배열에 정의되어 있고 tool_use → tool_result 사이클을 거친다. 이는 Subagent(Task 도구)나 MCP(mcp__xxx 도구)와 동일한 패턴이다.
tools 배열
├── 내장 도구 (Bash, Read, Glob...)
│ └── Host 내부에서 직접 실행
│
├── Task 도구 (Subagent)
│ └── 새 메시지 루프에서 LLM 응답 반환
│
├── mcp__xxx 도구 (MCP)
│ └── 외부 서버의 실행 결과 반환
│
└── Skill 도구 (Skills)
└── SKILL.md 로드 후 후속 도구 사용 안내
Agent Skill은 다른 도구 사용을 안내하는 메타 도구다. tool_result로 지시문을 컨텍스트에 주입하고 이후 Bash, Read 같은 다른 도구들의 사용을 안내한다. 결국 Skill → Bash → Read... 형태의 도구 체이닝이 발생한다.
Subagent와 MCP가 "무엇을 할 수 있는가"를 확장한다면 Skills는 "어떻게 잘 할 것인가"를 확장한다.
It's Christmas, but I don't really have anything to do, so I'm just coding. (I'm not Christian.)
Upyo 0.4.0 released. Upyo is an email sending library for Node.js, Deno, Bun, and edge functions. New in this version:
- JMAP transport for modern email servers
- DKIM signing support in SMTP transport
- Message-level idempotency keys for reliable retries
TIL: <form>에 .submit()을 하면 이벤트를 거치지 않고 검증도 안하고 냅다 제출이 된다. 이벤트를 거치게 만들려면 .requestSubmit()을 해야 된다
deno install 명령어를 실행할 때 패치 패키지가 암묵적으로 npm 패키지로 덮어쓰여지는 버그를 수정하였습니다! 😊
루비, 레일즈에서 페디버스를 구현하려면 https://gitlab.com/experimentslabs/federails 이 프로젝트가 구현 정도가 잘 되어 있으나 2명이서 틈틈히 개발하고 있어서 진행 상황이 느린 상태. 컨트리뷰터가 되어야 하나 포크를 해야 하나...
크리스마스는 새 프로그래밍 언어를 공개하기에 좋은 날이죠. 아직 미완성이지만 요즘 작업하고 있던 프로젝트를 소개합니다. https://github.com/Kroisse/tribute
순수 함수형이고, 모나드는 없고, 소유권도 없고, 타입클래스나 트레잇도 없습니다. 물론 객체 시스템도 없고요. 대신 제네릭과 대수적 효과를 넣을 예정입니다. ad-hoc polymorphism을 배제하고 어디까지 갈 수 있는지 시험해보려는 게 목적 중 하나인데 생각보다 할만할 것 같아요.
그리고 매우 vibe-coded되어 있습니다. Claude Code와 Codex가 없었으면 엄두도 못 냈을 듯.
문법적으로는 Rust와 Gleam에, 의미론적으로는 Gleam과 Unison에 영감을 많이 받았습니다. 사실 Gleam과 Unison 둘 다 네이티브 바이너리로 컴파일을 아직 못 하고 있어서 시작한 프로젝트이기도 합니다. 하지만 정작 Tribute도 첫 타겟은 네이티브가 아니라 WebAssembly 3.0입니다. GC 구현을 만들기 귀찮았거든요.
크리스마스를 맞아 크리스마스 트리... 가 아닌 Hackers' Pub 초대 트리를 꾸몄습니다.
기존 초대 트리는 작은 규모에서는 충분히 제 역할을 했으나 회원이 점차 늘어나면서 구조를 파악하기 어렵고, 페이지가 너무 길어져 변화가 필요하다고 생각했습니다. 다음 개편되는 Hackers' Pub의 초대 트리 페이지에서는 초대 관계를 잘 드러내면서, 많은 회원이 한 눈에 들어올 수 있도록 만들었습니다. Hackers' Pub의 회원은 앞으로도 많이 늘어날 예정이니까요. 그렇겠죠? 내년에도 Hackers' Pub에서 많은 분들을 만날 수 있기를, 더 풍성한 초대 트리를 볼 수 있기를 기대해봅니다.
Now trying to implement a JMAP transport for Upyo…
Complete! It will be included in the next release of Upyo.
모두들 즐거운 성탄절 되시길 바랍니다! 🎄
洪 民憙 (Hong Minhee) shared the below article:
MCP도 Tool Use를 사용합니다.
자손킴 @jasonkim@hackers.pub
지난 글에서는 Subagent가 Tool Use 위에서 어떻게 동작하는지 알아보았다. 이번 글에서는 MCP(Model Context Protocol)가 Tool Use와 어떻게 연결되는지 내장 도구인 Subagent를 예시로 비교하며 설명할 것이다. 또한 내장 도구가 있음에도 불구하고 MCP가 필요한 이유에 대해서도 알아본다.
내장 도구 vs MCP 도구
Subagent 글에서 살펴본 Task 도구는 에이전트에 내장된 도구였다. MCP 도구는 어떻게 다를까? 결론부터 말하면 LLM 입장에서는 둘 다 그냥 도구다. 차이는 실행이 어디서 일어나는가뿐이다.
내장 도구든 MCP 도구든 API 요청의 tools 배열에 동일한 형태로 들어간다:
{
"tools": [
{
"name": "Read",
"description": "Reads a file from the local filesystem...",
"input_schema": { ... }
},
{
"name": "Task",
"description": "Launch a new agent to handle complex tasks...",
"input_schema": { ... }
},
{
"name": "mcp__claude-in-chrome__navigate",
"description": "Navigate to a URL in the browser...",
"input_schema": { ... }
}
]
}
LLM은 도구 이름과 description, input_schema만 보고 어떤 도구를 호출할지 결정한다. 이 도구가 내장인지 MCP인지는 알 수 없고 알 필요도 없다.
핵심 차이는 도구가 어디서 실행되는가다.
| 내장 도구 (예: Task) | MCP 도구 | |
|---|---|---|
| 실행 위치 | Host 내부 | Host 외부 (별도 프로세스) |
| 통신 방식 | 함수 호출 | 프로토콜 (STDIO/HTTP) |
| 실행 주체 | Host (또는 LLM) | 외부 시스템 |
| 결과 | Host가 생성한 데이터 | 외부 시스템이 반환한 데이터 |
다이어그램으로 보면 더 명확하다:
Host 프로세스
─────────────────────────────────────────────────
Agent
│
┌────────────┴────────────┐
▼ ▼
Task mcp__xxx 도구
(내장 도구) │
│ │
▼ │ STDIO / HTTP
새 메시지 루프 │
(LLM) │
│
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─
│
외부 프로세스 ▼
MCP Server
(Chrome, DB...)
- 내장 도구 흐름: Agent → Task → 새 메시지 루프 (모두 Host 프로세스 내부)
- MCP 도구 흐름: Agent → mcp__xxx 도구 → [프로세스 경계] → MCP Server (Host 외부)
실제 예시로 보는 차이
지난 글에서 본 Explorer subagent 호출과 MCP 도구 호출을 비교해보자.
내장 도구 (Task) 호출:
{
"type": "tool_use",
"id": "toolu_01ABC123",
"name": "Task",
"input": {
"subagent_type": "Explore",
"prompt": "entity 구조를 탐색해주세요",
"description": "Entity 구조 탐색"
}
}
Task 도구가 호출되면 Host 내부에서 새로운 메시지 루프가 생성되고, Haiku 모델이 Glob, Read 등 다른 내장 도구로 탐색을 수행한다. 결과는 LLM이 생성한 분석 텍스트다.
MCP 도구 호출:
{
"type": "tool_use",
"id": "toolu_01DEF456",
"name": "mcp__claude-in-chrome__navigate",
"input": {
"tabId": 12345,
"url": "http://localhost:3000"
}
}
MCP 도구가 호출되면 Host는 외부의 MCP Server(Chrome 브라우저 프로세스)에 명령을 전달한다. 결과는 브라우저가 반환한 데이터(스크린샷, 콘솔 로그 등)다.
Agent 입장에서는 둘 다 tool_use 요청을 받아 실행하고 tool_result를 반환하는 동일한 패턴이다.
MCP 도구가 tools 배열에 포함되는 방식
MCP 서버가 제공하는 도구들은 어떻게 tools 배열에 들어갈까? MCP 도구는 mcp__server-name__tool-name 형태의 이름을 가진다.
{
"name": "mcp__claude-in-chrome__navigate",
"description": "Navigate to a URL, or go forward/back in browser history...",
"input_schema": {
"type": "object",
"properties": {
"tabId": {
"description": "Tab ID to navigate",
"type": "number"
},
"url": {
"description": "The URL to navigate to",
"type": "string"
}
},
"required": ["tabId", "url"]
}
}
이 네이밍 규칙이 필요한 이유는 여러 MCP 서버가 동시에 연결될 수 있기 때문이다. 예를 들어 filesystem 서버와 github 서버가 둘 다 read라는 도구를 제공한다면 충돌이 발생한다. mcp__filesystem__read와 mcp__github__read로 구분하면 이 문제가 해결된다.
Claude Code에서 /mcp를 입력하면 연결된 MCP 서버 목록을 볼 수 있다. Claude in Chrome이 제공하는 도구들을 살펴보자:
| 도구 이름 | 설명 |
|---|---|
mcp__claude-in-chrome__navigate |
URL로 이동하거나 브라우저 히스토리 앞/뒤로 이동 |
mcp__claude-in-chrome__computer |
마우스/키보드로 브라우저와 상호작용, 스크린샷 촬영 |
mcp__claude-in-chrome__read_page |
페이지의 접근성 트리 표현을 가져옴 |
mcp__claude-in-chrome__find |
자연어로 페이지 요소 찾기 |
mcp__claude-in-chrome__form_input |
폼 요소에 값 입력 |
mcp__claude-in-chrome__javascript_tool |
페이지 컨텍스트에서 JavaScript 실행 |
이 도구들은 에이전트가 MCP 서버에 연결할 때 서버로부터 목록을 받아와 tools 배열에 추가된다. 에이전트가 시작될 때 대략 다음과 같은 과정이 일어난다:
- 에이전트가 설정된 MCP 서버들에 연결
- 각 서버에
tools/list요청을 보내 제공하는 도구 목록 수신 - 받은 도구들에
mcp__server-name__prefix를 붙여tools배열에 추가 - API 요청 시 내장 도구와 함께 전송
MCP 도구 호출 흐름
Claude가 MCP 도구를 호출하면 에이전트는 다음 단계를 수행한다:
Claude Agent MCP Server
│ │ │
│ tool_use │ │
│ (mcp__claude-in- │ │
│ chrome__navigate) │ │
│ ─────────────────────► │ │
│ │ │
│ prefix 파싱 │
│ server: claude-in-chrome │
│ tool: navigate │
│ │ │
│ │ tools/call │
│ │ ───────────────────────► │
│ │ │
│ │ 실행 결과 │
│ │ ◄─────────────────────── │
│ │ │
│ tool_result │ │
│ ◄───────────────────── │ │
│ │ │
결국 MCP 도구 호출도 일반 Tool Use와 동일한 패턴을 따른다. 차이점은 에이전트가 도구를 직접 실행하는 대신 외부 MCP 서버에 위임한다는 것뿐이다.
MCP는 왜 도구를 외부로 분리하는가
지금까지 MCP 도구가 어떻게 동작하는지 살펴보았다. 그런데 왜 이런 구조가 필요할까?
모든 도구를 내장할 수 없다
에이전트에 도구를 추가하는 가장 단순한 방법은 에이전트 내부에 직접 구현하는 것이다. 하지만 이 방식에는 한계가 있다:
- 모든 도구를 미리 구현할 수 없다: 파일 시스템, 데이터베이스, 브라우저, Slack, GitHub, Jira... 세상에는 수많은 시스템이 있고 에이전트 개발자가 이 모든 연동을 직접 구현하기는 불가능하다.
- 사용자마다 필요한 도구가 다르다: 어떤 사용자는 PostgreSQL을, 다른 사용자는 MongoDB를 사용한다. 모든 조합을 에이전트에 내장할 수 없다.
- 도구 업데이트가 어렵다: 외부 API가 변경되면 에이전트 전체를 다시 배포해야 한다.
MCP는 이 문제를 도구 제공자와 도구 사용자의 분리로 해결한다.
MCP의 핵심 구조
MCP는 클라이언트-서버 아키텍처를 따른다.
참여자 (Participants):
- MCP Host: MCP 클라이언트를 관리하는 AI 애플리케이션 (예: Claude Desktop, VS Code, Claude Code)
- MCP Client: MCP 서버와 연결을 유지하고 컨텍스트를 얻어오는 컴포넌트
- MCP Server: MCP 클라이언트에게 컨텍스트를 제공하는 프로그램 (도구 제공자)
Host와 Client의 관계:
- Host는 MCP 서버 연결마다 별도의 MCP Client를 생성한다
- 예를 들어 Claude Code(Host)가 Chrome 서버와 filesystem 서버에 연결하면 두 개의 MCP Client 객체가 생성된다
- 각 MCP Client는 하나의 MCP Server와 전용 연결을 유지한다
이 분리 덕분에:
- 도구 제공자는 MCP 서버만 만들면 됨 (에이전트 코드 수정 불필요)
- 에이전트 개발자는 MCP 클라이언트만 구현하면 모든 MCP 서버의 도구 사용 가능
- 사용자는 필요한 MCP 서버만 설치하여 에이전트 기능 확장 가능
MCP와 인증
원격 MCP 서버를 사용할 때는 인증이 필요한 상황이 발생한다. MCP 서버가 사용자의 GitHub 저장소에 접근하거나 Slack 워크스페이스에 메시지를 보내야 할 때, "이 요청이 정말 이 사용자로부터 온 것인가?"를 확인해야 한다.
MCP는 프로토콜 수준에서 OAuth 2.1 인증 체계를 표준화했다. 덕분에 어떤 MCP 클라이언트든 동일한 방식으로 MCP 서버에 인증할 수 있고, MCP 서버 개발자는 인증 로직을 한 번만 구현하면 모든 클라이언트와 호환된다.
Tool Use를 넘어서
지금까지 MCP를 Tool Use의 확장으로 설명했다. 실제로 MCP 도구는 가장 많이 사용되는 기능이고, LLM이 외부 시스템과 상호작용하는 핵심 방식이다.
하지만 MCP가 제공하는 것이 도구만은 아니다. MCP 명세를 보면 Tool Use와 무관하게 동작하는 기능들이 있다. 이 기능들은 tool_use -> tool_result 사이클을 거치지 않고 다른 방식으로 LLM에게 컨텍스트를 제공하거나 LLM의 능력을 활용한다.
MCP의 확장 기능
MCP는 도구(Tools) 외에도 몇가지 핵심 기능들이 있다. Resources, Prompts, 그리고 Sampling이다.
Resources
Resources는 Tool Use를 거치지 않는 데이터 제공 기능이다. 도구는 LLM이 "행동"을 요청할 때 호출되지만 Resource는 LLM이 응답을 생성하기 전에 컨텍스트로 미리 주입된다.
예를 들어 PostgreSQL MCP 서버가 데이터베이스 스키마를 Resource로 노출한다고 하자. 사용자가 "users 테이블에 email 컬럼 추가해줘"라고 요청하면 LLM은 별도의 도구 호출 없이도 현재 스키마 구조를 이미 알고 있다. SELECT * FROM information_schema.columns를 먼저 실행할 필요가 없는 것이다. Resource가 컨텍스트에 미리 주입되어 있기 때문이다.
Prompts
Prompts도 Tool Use와 무관하다. MCP 서버가 미리 정의한 재사용 가능한 프롬프트 템플릿으로 클라이언트가 직접 요청해서 가져온다.
예를 들어 코드 리뷰 MCP 서버가 "보안 취약점 분석" 프롬프트 템플릿을 제공하면 클라이언트는 이 템플릿을 불러와 LLM에게 전달할 수 있다. LLM이 도구를 호출하는 것이 아니라 클라이언트가 MCP 서버로부터 프롬프트를 받아오는 것이다.
Sampling (역방향 LLM 호출)
Sampling은 가장 독특한 기능이다. 일반적인 MCP 흐름은 LLM → Agent → MCP Server지만 Sampling은 이 방향을 뒤집는다:
일반 흐름: LLM → Agent → MCP Server
Sampling: MCP Server → Agent → LLM
MCP 서버가 복잡한 판단이 필요할 때 역으로 LLM에게 질문할 수 있다. 예를 들어 코드 분석 MCP 서버가 특정 패턴을 발견했을 때 "이 코드가 보안 취약점인지 판단해달라"고 LLM에게 요청하는 식이다. MCP 서버는 LLM의 답변을 기반으로 최종 결과를 만들거나 다른 도구나 함수를 사용하는 등의 판단을 할 수 있게 된다.
Sampling은 Tool Use의 tool_use -> tool_result 패턴이 아니라 MCP 프로토콜 자체의 sampling/createMessage 요청을 통해 동작한다.
마무리
지금까지 MCP가 Tool Use 위에서 어떻게 동작하고 또 Tool Use를 넘어 어떤 기능들을 제공하는지 살펴보았다.
MCP 도구는 Tool Use다. 내장 도구와 동일하게 tools 배열에 포함되고 tool_use로 호출되며 tool_result로 결과가 반환된다. LLM 입장에서는 구분이 없다. 차이점은 실행 위치뿐이다. 내장 도구는 Host 프로세스 내부에서, MCP 도구는 외부 MCP Server에서 실행된다.
하지만 MCP는 도구만 제공하지 않는다. Resources와 Prompts는 Tool Use 없이 컨텍스트를 제공하고 Sampling은 MCP 서버가 역으로 LLM을 활용할 수 있게 한다. MCP는 Tool Use를 확장하면서도 Tool Use만으로는 해결할 수 없는 영역까지 커버하는 프로토콜이다.
모든 도구를 에이전트에 내장할 수 없기 때문에 에이전트와 도구를 분리할 필요가 생겼고 MCP는 도구 제공자와 사용자를 분리하여 생태계 확장을 가능하게 한다.
이 글에서는 Tool Use의 기본 구조를, 이어지는 글에서는 Subagent가 Tool Use 위에서 동작함을 살펴보았고 이번 글에서 MCP가 Tool Use를 확장하면서도 그 이상의 기능을 제공함을 살펴보았다. Claude Code의 핵심 확장 기능들은 Tool Use라는 메커니즘 위에서 동작하지만 MCP 생태계는 그보다 더 넓은 가능성을 열어두고 있다.
Fedify 1.10.0: Observability foundations for the future debug dashboard
Fedify is a #TypeScript framework for building #ActivityPub servers that participate in the #fediverse. It reduces the complexity and boilerplate typically required for ActivityPub implementation while providing comprehensive federation capabilities.
We're excited to announce #Fedify 1.10.0, a focused release that lays critical groundwork for future debugging and observability features. Released on December 24, 2025, this version introduces infrastructure improvements that will enable the upcoming debug dashboard while maintaining full backward compatibility with existing Fedify applications.
This release represents a transitional step toward Fedify 2.0.0, introducing optional capabilities that will become standard in the next major version. The changes focus on enabling richer observability through OpenTelemetry enhancements and adding prefix scanning capabilities to the key–value store interface.
Enhanced OpenTelemetry instrumentation
Fedify 1.10.0 significantly expands OpenTelemetry instrumentation with span events that capture detailed ActivityPub data. These enhancements enable richer observability and debugging capabilities without relying solely on span attributes, which are limited to primitive values.
The new span events provide complete activity payloads and verification status, making it possible to build comprehensive debugging tools that show the full context of federation operations:
activitypub.activity.receivedevent onactivitypub.inboxspan — records the full activity JSON, verification status (activity verified, HTTP signatures verified, Linked Data signatures verified), and actor informationactivitypub.activity.sentevent onactivitypub.send_activityspan — records the full activity JSON and target inbox URLactivitypub.object.fetchedevent onactivitypub.lookup_objectspan — records the fetched object's type and complete JSON-LD representation
Additionally, Fedify now instruments previously uncovered operations:
activitypub.fetch_documentspan for document loader operations, tracking URL fetching, HTTP redirects, and final document URLsactivitypub.verify_key_ownershipspan for cryptographic key ownership verification, recording actor ID, key ID, verification result, and the verification method used
These instrumentation improvements emerged from work on issue #234 (Real-time ActivityPub debug dashboard). Rather than introducing a custom observer interface as originally proposed in #323, we leveraged Fedify's existing OpenTelemetry infrastructure to capture rich federation data through span events. This approach provides a standards-based foundation that's composable with existing observability tools like Jaeger, Zipkin, and Grafana Tempo.
Distributed trace storage with FedifySpanExporter
Building on the enhanced instrumentation, Fedify 1.10.0 introduces FedifySpanExporter, a new OpenTelemetry SpanExporter that persists ActivityPub activity traces to a KvStore. This enables distributed tracing support across multiple nodes in a Fedify deployment, which is essential for building debug dashboards that can show complete request flows across web servers and background workers.
The new @fedify/fedify/otel module provides the following types and interfaces:
import { MemoryKvStore } from "@fedify/fedify";
import { FedifySpanExporter } from "@fedify/fedify/otel";
import {
BasicTracerProvider,
SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-base";
const kv = new MemoryKvStore();
const exporter = new FedifySpanExporter(kv, {
ttl: Temporal.Duration.from({ hours: 1 }),
});
const provider = new BasicTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));The stored traces can be queried for display in debugging interfaces:
// Get all activities for a specific trace
const activities = await exporter.getActivitiesByTraceId(traceId);
// Get recent traces with summary information
const recentTraces = await exporter.getRecentTraces({ limit: 100 });The exporter supports two storage strategies depending on the KvStore capabilities. When the list() method is available (preferred), it stores individual records with keys like [prefix, traceId, spanId]. When only cas() is available, it uses compare-and-swap operations to append records to arrays stored per trace.
This infrastructure provides the foundation for implementing a comprehensive debug dashboard as a custom SpanExporter, as outlined in the updated implementation plan for issue #234.
Optional list() method for KvStore interface
Fedify 1.10.0 adds an optional list() method to the KvStore interface for enumerating entries by key prefix. This method enables efficient prefix scanning, which is useful for implementing features like distributed trace storage, cache invalidation by prefix, and listing related entries.
interface KvStore {
// ... existing methods
list?(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
}When the prefix parameter is omitted or empty, list() returns all entries in the store. This is useful for debugging and administrative purposes. All official KvStore implementations have been updated to support this method:
MemoryKvStore— filters in-memory keys by prefixSqliteKvStore— usesLIKEquery with JSON key patternPostgresKvStore— uses array slice comparisonRedisKvStore— usesSCANwith pattern matching and key deserializationDenoKvStore— delegates to Deno KV's built-inlist()APIWorkersKvStore— uses Cloudflare Workers KVlist()with JSON key prefix pattern
While list() is currently optional to give existing custom KvStore implementations time to add support, it will become a required method in Fedify 2.0.0 (tracked in issue #499). This migration path allows implementers to gradually adopt the new capability throughout the 1.x release cycle.
The addition of list() support was implemented in pull request #500, which also included the setup of proper testing infrastructure for WorkersKvStore using Vitest with @cloudflare/vitest-pool-workers.
NestJS 11 and Express 5 support
Thanks to a contribution from Cho Hasang (
@crohasang크롸상), the @fedify/nestjs package now supports NestJS 11 environments that use Express 5. The peer dependency range for Express has been widened to ^4.0.0 || ^5.0.0, eliminating peer dependency conflicts in modern NestJS projects while maintaining backward compatibility with Express 4.
This change, implemented in pull request #493, keeps the workspace catalog pinned to Express 4 for internal development and test stability while allowing Express 5 in consuming applications.
What's next
Fedify 1.10.0 serves as a stepping stone toward the upcoming 2.0.0 release. The optional list() method introduced in this version will become required in 2.0.0, simplifying the interface contract and allowing Fedify internals to rely on prefix scanning being universally available.
The enhanced #OpenTelemetry instrumentation and FedifySpanExporter provide the foundation for implementing the debug dashboard proposed in issue #234. The next steps include building the web dashboard UI with real-time activity lists, filtering, and JSON inspection capabilities—all as a separate package that leverages the standards-based observability infrastructure introduced in this release.
Depending on the development timeline and feature priorities, there may be additional 1.x releases before the 2.0.0 migration. For developers building custom KvStore implementations, now is the time to add list() support to prepare for the eventual 2.0.0 upgrade. The implementation patterns used in the official backends provide clear guidance for various storage strategies.
Acknowledgments
Special thanks to Cho Hasang (
@crohasang크롸상) for the NestJS 11 compatibility improvements, and to all community members who provided feedback and testing for the new observability features.
For the complete list of changes, bug fixes, and improvements, please refer to the CHANGES.md file in the repository.
크리스마스에는 모두 #오라클클라우드 에 입문해보아요
(아무도 관심없겠지만) 요거의 예시로는 tree-sitter의 파싱 테이블 + AST node 메타데이터 export가 있다. 지금은 파싱 테이블 대신 parser.c만 뱉고 중요한 메타데이터를 몇개 빼먹는다. 하는 김에 rewrite in haskell도 하면 더 좋고..
좀 웃긴 생각이 났는데, '해결해주면 나랑 친해질수 잇는 문제들'이란 목록을 만들어서 공유하고 싶다. 네임드가 되면 실제로 잘 동작하려나.
오늘은
@xo 님이랑 화상으로 커피챗을 했다. 같은 오픈 소스 프로젝트 메인테이너로서 고충을 공유했다… 😂
https://news.hada.io/topic?id=25285
추상화가 강력한 더 좋은 언어를 쓰는 것 이상의 확실한 방법이 없고, 그 외에는 그냥 임시방편이라고 생각함.
helix/kakoune는 selection 기능 정리에 힘을 줬는데, 나는 emacs의 mark식 selection이 가장 편하다. h/k는 형식을 지키기 위해서 복잡해진 감이 있음. 통일성 없는 vi보다는 낫긴 한데.
Meta, Valve의 Steam Deck용으로 설계된 Linux 스케줄러를 대규모 서버에 사용
------------------------------
- Valve의 Steam Deck를 위해 설계된 *SCX-LAVD 리눅스 스케줄러* 가 Meta의 대규모 서버 환경에서도 효과적으로 작동한다는게 공개됨
- 이 스케줄러는 *게임 콘솔 수준의 효율적 자원 관리* 를 목표로 설계된 것인데, Meta는 이를 통해 *서버 워크로드의 성능 향상* 과 *지연 시간 최소화* 를 추구
- 휴대용 게…
------------------------------
https://news.hada.io/topic?id=25293&utm_source=googlechat&utm_medium=bot&utm_campaign=1834
iOS에서 내가 쓸 단모음 키보드 앱을 만들고 있다.
기존에 쓰던 앱들이 있긴 했는데, 하나는 어느샌가 키보드 자판 위에 툴바를 붙이더니 월 구독 결제를 안하면 숨길 수 없게 해놨고 다른 하나는 최근 iOS 업데이트 이후로 텍스트를 지울때 단어가 망가지는 문제가 생겨서 대체제를 찾다가 마음에 드는게 없어서 결국 직접 만들기로 결정함..
직접 만들어서 테스트하다보니 유저 입장에서 어떤 부분이 불편한지 - 해소해야할지 보인다. 재미는 있는데 생각보다 신경써야 할 게 많다.
그런데 실제로 이를 조건 분기로 구현해보면 상당히 복잡해진다. 더 일반화된 방법이 필요하다. 버전 관계를 다시 정의해보자. 변환기를 기준으로 생각해보면 모든 버전 사이에 순서가 있다고 할 수는 없다. 버전을 poset으로 모델링해보면 어떨까? a ≺ b 관계가 성립하는 경우에는 b의 변환기를 이용해 a를 마이그레이션할 수 있다. 이 관계가 성립하려면 (1) a가 안정 버전이고, (2) b가 알파 버전이 아니고, (3) major(a) < major(b)이어야 한다.
(1.x, 3.x)는 모든 조건을 만족하기 때문에 변환기를 거쳐 마이그레이션할 수 있다. (1.x, 3.x-beta)도 모든 조건을 만족한다. 반면 (3.x-beta, 1.x)는 (1), (3)을 위반하기 때문에 변환기를 사용할 수 없다. (2.x-alpha.1, 3.x-alpha.0)는 (1), (2)를 위반한다. 이렇게 출발 버전부터 시작해 a ≺ b 관계를 만족하고, b가 목적 버전이 아닌 경우 안정 버전이어야 한다는 조건을 적용해 나가면 도착 버전까지의 경로도 얻을 수 있다.
오랜만에 복잡한 문제를 단순한 형태로 모델링해서 해결했다. 문제 상황은 대략 라이브러리의 메이저 버전을 위해 자동 마이그레이션을 지원하는 것이다. 각 메이저 버전에는 이전 버전의 코드를 자동으로 변환해주는 변환기가 함께 배포된다. 만약 1.x에서 3.x로 업데이트할 때는 2.x 변환기, 3.x 변환기를 순차적으로 실행해야 한다. 즉, 하위 버전에서 출발해 상위 버전에 도착하는 경로를 구해야 한다.
그런데 베타 버전(x.y-beta)과 알파 버전(x.y-alpha.z)을 고려해야 한다. 베타 버전에는 직전 버전에 대한 변환기가 있고, 알파 버전에는 변환기가 없다. 나이브하게 생각해보면 출발 버전 유형과 도착 버전 유형의 조합에 대해 모든 경우를 분기해서 경로를 구할 수 있을 것처럼 보인다.
그런데 실제로 이를 조건 분기로 구현해보면 상당히 복잡해진다. 더 일반화된 방법이 필요하다. 버전 관계를 다시 정의해보자. 변환기를 기준으로 생각해보면 모든 버전 사이에 순서가 있다고 할 수는 없다. 버전을 poset으로 모델링해보면 어떨까? a ≺ b 관계가 성립하는 경우에는 b의 변환기를 이용해 a를 마이그레이션할 수 있다. 이 관계가 성립하려면 (1) a가 안정 버전이고, (2) b가 알파 버전이 아니고, (3) major(a) < major(b)이어야 한다.
오랜만에 복잡한 문제를 단순한 형태로 모델링해서 해결했다. 문제 상황은 대략 라이브러리의 메이저 버전을 위해 자동 마이그레이션을 지원하는 것이다. 각 메이저 버전에는 이전 버전의 코드를 자동으로 변환해주는 변환기가 함께 배포된다. 만약 1.x에서 3.x로 업데이트할 때는 2.x 변환기, 3.x 변환기를 순차적으로 실행해야 한다. 즉, 하위 버전에서 출발해 상위 버전에 도착하는 경로를 구해야 한다.
그런데 베타 버전(x.y-beta)과 알파 버전(x.y-alpha.z)을 고려해야 한다. 베타 버전에는 직전 버전에 대한 변환기가 있고, 알파 버전에는 변환기가 없다. 나이브하게 생각해보면 출발 버전 유형과 도착 버전 유형의 조합에 대해 모든 경우를 분기해서 경로를 구할 수 있을 것처럼 보인다.
Found this helpful resource by Ben Boyter (@boyter): a collection of sequence diagrams explaining how #ActivityPub/#WebFinger works in practice—covering post creation, follows, boosts, deletions, and user migration.
If you're trying to implement ActivityPub, the spec can be frustratingly vague, and different servers do things differently. This aims to be a “clean room” reference for getting federation right.
On a related note, if you learn better by building things, @fedifyFedify: ActivityPub server framework has a step-by-step tutorial where you create a federated microblog from scratch—implementing actors, inbox/outbox, following, and posts along the way:
Found this helpful resource by Ben Boyter (@boyter): a collection of sequence diagrams explaining how #ActivityPub/#WebFinger works in practice—covering post creation, follows, boosts, deletions, and user migration.
If you're trying to implement ActivityPub, the spec can be frustratingly vague, and different servers do things differently. This aims to be a “clean room” reference for getting federation right.
GUI앱을 못보니까 콘솔로 출력해서 본다는 점이 정말... 나보다 낫군
According to
@tchambersTim Chambers's My 2026 Open Social Web Predictions:
Fedify will power the federation layer for at least one mid-sized social platform (500K+ users) that adds ActivityPub support in 2026. The “build vs. buy” calculation for federation shifts decisively toward “just use Fedify.”
We're honored by this recognition and will keep working hard to make #ActivityPub adoption easier for everyone. Thank you, Tim!
Here's a #TypeScript API design challenge I'm working on: adding async support to #Optique (CLI argument parser) without breaking the existing sync API.
The tricky part is combinators—when you compose parsers with object() or or(), the combined parser should automatically become async if any child parser is async, but stay sync if all children are sync. This “mode propagation” needs to work at both type level and runtime.
I've prototyped two approaches and documented findings. If you've tackled similar dual-mode API designs, I'd love to hear how you approached it.
목공용 절단도면 만들어주는 프로그램 뇌 비우고 Claude Code한테 시켰더니 때깔이 좋네
Stacked Diff 관리하려고 Sapling을 찍먹해봤는데, 이건 누가 hands on 세션을 하는게 아닌 이상 익숙해지는게 좀 어려울 듯.....
https://github.com/stadia/textsearch_ko 잘 쓰고 있는 pg 확장인데 너무 오래 된 것 같아 업데이트를 해봤습니다. 원본 https://github.com/i0seph/textsearch_ko
洪 民憙 (Hong Minhee) shared the below article:
스마일 PRO 라식 수술 후기
자손킴 @jasonkim@hackers.pub
시력교정술을 받은 주변사람들이 신세계라며 추천을 해도 그동안 관심이 없었다. 어렸을때부터 안경을 쓰고 평생을 살아온지라 안경을 쓰는 것이 불편하다고 느껴 본 적이 없었기 때문이다.
라식을 해볼까? 라고 생각이 든 것은 스쿠버 다이빙을 시작하게 되면서였다. 다이빙에 취미를 붙이고나니 가장 불편한게 눈이었다. 렌즈를 끼고 있는게 불편한건 물론이고 아침 바쁜 와중에 렌즈가 안들어가서 진을 다 빼고 하루를 시작하는것도 문제였다. 게다가 렌즈를 부족하게 들고가거나 숙소에 렌즈를 놓고 오는 등 나의 정신머리 때문에 반쪽짜리 다이빙을 하게 되는 일이 반복되면서 시력교정을 해야겠다고 결심했다.
공장식 병원이야 어차피 거기서 거기라는 생각에 병원비교 사이트를 보고 적당한 곳을 골라 검사 예약을 했다. 원하면 당일 수술도 가능하다고 하던데 나는 검사만 먼저 받고 일주일후에 수술을 하기로 했다.
검사는 일반적인 안과 검사와 크게 다른 것은 없었다. 눈에 바람을 쏘는 것을 시작으로 뭔가를 들여다보고 빛을 비추고 등등 예닐곱가지의 검사가 진행되었다. 이어서 시력 검사를 하고 교정 후 도수를 결정했는데 이것은 안경 맞출때와 동일했다.
검사가 끝나고 의사선생님과 상담하며 눈의 상태에 대해서 설명을 들었다. 다행히 안압이나 눈 모양등도 정상이고 각막 두께도 평균이라 라섹, 라식 모두 원하는대로 진행이 가능하다고 했다. 다만 이제 노안이 오고 있기 때문에 돋보기를 쓰는 시간을 조금이라도 더 늦추려면 시력을 약간 낮추고 양눈 중 주로 가까이 보는 눈은 시력을 조금 더 낮춰 교정하는게 좋겠다는 제안을 받았다.
이어서 코디네이터에게 수술 종류와 방법에 대한 설명을 들었다. 나는 회복이 빠른게 최우선이었기 때문에 스마일PRO를 하기로 결정했다.
수술 당일에도 2~3가지의 검사를 다시하고 최종적으로 교정 시력에 대해서도 다시 한 번 확인을 하고 코디네이터에게 수술 후 주의사항과 안약 투여에 대한 설명을 듣는다.
눈에 물이 닿는 것과 격렬한 운동은 일주일 정도 피해야한다. 2주간은 금주하고 한 달동안 과음도 피해야 한다. 목욕탕, 사우나처럼 뜨거운 증기와 물은 한달간 피한다.
의사선생님을 만나 눈 상태에 대해서 최종점검을 하고 수술 대기실로 이동을 하여 위생모와 가운을 입고 잠시 기다리다 수술실로 들어갔다. 간호사님의 안내에 따라 수술장비에 누우면 눈을 감고 있으라 한 후 세팅을 시작하는데, 이때부터는 절대 고개를 들지말라고 한다.
잠시후 눈을 뜨라고 한 뒤 의사가 눈에 마취안약을 넣어줬다. 집게 같은걸로 눈을 벌리고 "이것만 문제 없으면 다른건 잘 참으실 수 있을거에요"라는 소리와 함께 눈앞에서 뭔가가 왔다갔다 하는데 아마도 마취가 되었는지 안구를 건드려 보는 것 같았다. 눈에 아무런 느낌은 없었다.
수술은 오른쪽 눈부터 진행되었다. 왼쪽눈에는 거즈 같은 것을 덮고 오른쪽 눈으로 앞에 보이는 초록불빛을 바라보라고 한다. 초록불빛을 바라볼때 다른 눈을 감으면 눈이 움직일 수 있으니 양쪽눈을 다 뜨되 수술하는 눈으로만 보라고 한다.
처음에는 크고 흐릿하게 보였던 초록불빛이 점점 작고 선명해 지면서 초록점으로 보인다.초록점을 눈의 중앙에 오도록 보라고 하는데, 이때가 가장 어려웠다. 초록점을 눈의 가운데에 오도록 보고 있는데 의사가 거기가 아니니 제대로 보라고 하는것이다. 몇 번을 다시 시도해도 잘 안됐는지 "환자분이 하는 수술인데 협조가 안되면 어떻게 하냐"며 의사가 약간 역정을 냈다.
초록점을 보는데 실패하면 기계가 눈을 잡아줄 수 없고 의사가 직접 조작하여 수술을 해야 하는데, 그러면 아무래도 기계만큼의 정확도가 나오지는 않는 모양이다.
어떻게하면 방향을 맞출 수 있을지 궁리하다 눈알을 오른쪽에서 왼쪽으로 천천히 굴려보다 의사가 됐다고 하면 멈춰보기로 했다. 눈알을 굴리는데 의사가 거기가 맞다고 하였다. 초록점은 안구의 중앙이 아닌 미간 정도의 위치에 있었다. 잘은 몰라도 사람마다 안구의 각도 같은게 차이가 있나보다.
이제 레이저를 조사하니 가만히 있으라 하고 의사와 간호사가 시간을 세어준다. 초록점을 보고 6~7초 정도 있으면 어느새 점이 사라지고 눈앞이 하얗게 보인다. 2~3초가 더 지나자 끝났다며 잘 참았다고 한다. 레이저가 조사되는 동안은 별다른 느낌은 없었다.
다음으로 왼쪽눈을 진행하는 오른쪽보다 훨씬 수월했다. 왼쪽눈도 우선 처음에는 초록점을 눈의 중앙에 오게 바라봤는데 그게 맞았는지 한 번에 진행되었다. 마찬가지로 약 10초정도가 걸려 레이저 조사가 끝났다.
레이저 조사가 끝나면 무언가로 눈을 후비적거리고 주사를 몇 개 놓고 안약등을 넣고 불빛을 쬐어준다. 이것은 레이저 조사가 마지막으로 끝난 왼쪽을 먼저 하고 오른쪽을 하였다. 아마도 각막 조각을 제거하고 소독등의 처치를 하는 것 같았다. 이 과정은 눈 한쪽당 1~2분 정도 걸렸던 것 같다.
모든 과정이 끝나면 간호사의 안내에 따라 장비에서 일어나 이동을 한다. 궁금한 마음으로 주변을 둘러봤는데 세상이 온통 뿌연게 온통 손자국이 번짐 안경을 쓰고있는 기분이었다. 뿌옇긴해도 이전보다 더 선명해진것은 체감이 됐다.
다시 한 번 의사선생님을 만나 눈에 이상이 없나 검사를 받고 몇가지 주의 사항을 들은 후 퇴원을 한다. 초반에는 빛번짐이 있을 수 있고 시력이 한번에 교정시력 만큼까지 잘보이는 건 아니지만 시간이 지나며 계속 더 잘보일 것 이라고 한다. 그리고 이제 눈이 시리기 시작할건데 1~3시간 정도면 가라앉을 거라는 이야기도 들었다.
퇴원하는 길에 약국에 들려서 안약 2종류와 인공눈물을 받았다. 안약은 일주일간 하루 4번을 투여하고 인공눈물은 수시로 넣으라고 한다. 안약을 여러개 넣을때는 최소한 5분 간격을 두고 넣으라고 하는데 종류가 여러개이다보니 안약 넣다보면 하루가 다 간다.
집에 오니 눈시림이 더 심해지고 눈에 다래끼나 나거나 눈썹이 들어간듯한 이물감이 느껴져서 눈을 뜨고 있을 수가 없었다. 눈을 떠도 온통 뿌옇게 보여서 사물의 형체는 분간이 되지만 글씨 같은건 읽을 수가 없었다. 안약을 넣어야 하는데 주의사항이 적혀있는 종이를 읽을 수 없어서 제미나이 라이브를 켜고서 읽어달라고 했다.
안약을 넣고서 침대에 누워 눈을 감았다. 눈을 감아도 빛이 밝으면 눈이 시려서 빛을 차단하고 누워있다 두어시간 자고 일어났다. 이물감은 여전했지만 눈시림과 빛번짐이 덜해서 눈을 뜨고 무언가를 볼 수는 있었다. 눈을 오래 뜨고 있으면 피로감이 있는건 마찬가지라 저녁 먹고 다시 안약을 넣고 일찍 잠을 청했다. 덕분에 밀린 수면 부채를 많이 갚았다.
눈의 피로감 때문인지 깊게는 못자고 자다 두어번 깼다. 일어나보니 새벽 5시쯤 되었는데 더이상 잠이 안오길래 후기나 써야겠다고 생각했다. 이물감이나 눈시림은 많이 나아졌고 눈도 어제보다는 더 선명하게 보이기 시작했다. 그러나 빛번짐으로 인해 탁하고 뿌옇게 보이는건 여전했다.
모니터나 스마트폰의 화면을 볼 수는 있는데 밝으면 눈이 아프고 집중하면 눈이 시려서 글자 크기를 키우고 화면 밝기는 최대한 낮췄다. 모니터를 오래 보면 눈이 금방 피로해져서 드문드문 후기를 적다가 진료 시간이 다되어 다시 병원을 찾았다.
라식 수술 후에는 1일, 1주일, 1개월, 3개월에 진료를 받는 것을 권장하고 6개월 이후에는 6개월~1년 주기로 한 번씩 검사를 받는 것을 권장한다고 한다.
불편한게 있는지 물어봐서 눈시림과 이물감이 있었지만 지금은 많이 좋아졌다고 답하였다. 먼거리와 가까운 거리의 시력을 다시 한 번 검사하고 의사선생님을 만나 안구 상태에 대해서 진료를 받았다. 어제 수술 중 초록점을 바라보는 문제에 대해 이야기를 나눴다.
내가 걱정했던 부분은 혹여나 잘못된 위치를 바라봐서 각막이 엉뚱하게 절삭된 것은 아닐까 하는 것이었다. 하루가 지나면서 그렇지는 않을 것 같다는 생각을 했으나 혹시 모를 일이라 한 번 더 의사선생님한테 물어봤다.
다행히 어제 수술도 정확히 되었고 오늘 안구 상태도 이상 없으니 걱정 말라는 답을 들었다. 혹여나 초록점을 제대로 못보는 환자가 있다면 나처럼 안구의 중앙이 아닌 다른 곳을 봐야 하는 것일 수 있으니 다른 곳을 보도록 유도하며 안구를 맞추면 좋겠다는 말씀을 드리고 다음주에 뵙자하고 진료를 마무리했다.
시간이 지날수록 시야가 점점 선명해 지는게 체감이 되고 있다. 그러나 뿌옇게 보이는 느낌은 아직 남아 있어서 안경을 닦거나 고쳐써야 할 것 같은데 그럴 안경이 없어서 당혹스러움을 느끼고 있다.
나는 이제 지금까지와는 다른 눈으로 세상을 보게 되었다.
CI 실행 시간을 1/3로 줄였음
GitHub Workflows 어렵네.. 3년 동안 새로운 걸 배워보려고 하지 않은 업보가 이렇게. 즐거운 마음으로 해야 하는데 ㅎㅎ
NextJS 처음 써보는데, 챗봇 UI처럼 인터액티브한 웹앱을 만들때 도움이 되는 부분이 뭔지를 모르겠다. 처음엔 SPA + API 서버 만드는것과 비교해, 자명한 데이터 바인딩 보일러플레이트를 줄여줄거라 생각했다. 근데, 순수 SSR로 처리할 수 없는, 클라에서 상태를 업데이트하는 약간만 복잡한 플로우에서도 전혀 도움이 안된다.
일하다 막힐 때 위키피디아, 한국민족문화대백과사전, man 페이지 설명 읽는게 머리 식히는데 좋은 것 같음
[구인 커피챗 요청] 2025-12-26 ~ 2026-01-06 한국에 잠시 방문하는데, 좋은 개발자 분들을 만나고 싶습니다.
저는 펜시브 라는 미국 교육 AI 스타트업 CTO이고, 최근에 크게 투자유치를 하여 현재 초기 개발팀을 꾸리고 있습니다.
- 미국비자 지원받고 바로 샌프란시스코로 넘어오고 싶은 개발자 (미국에서 일하셔야 합니다!)
- 하루종일 학습에 대해서 생각하고 싶은 개발자
- 작은 팀으로 데카콘을 만들고 싶은 개발자
커피챗 연락주십시오: >> minjune@pensieve.co <<
펜시브 제품소개: https://claude.com/customers/pensieve
기술스택: typescript + react / fastapi + python / firebase / postgres
정보가 부족한 에러 메시지로 인해서 약 하루를 버렸다. 정보가 부족한 것도 있지만, 믿음(?)이 부족해서 멀쩡한 곳만 잔뜩 쳐다보고 있었다.
드디어 Claude Code에 LSP 지원이…!!
Claude Code에 네이티브 LSP 지원 기능 추가
------------------------------
- 터미널에서 실행되는 *AI 기반 코딩 도구* 인 Claude Code 가 최신버전에서 *LSP (Language Server Protocol) 도구* 를 추가
- 이를 통해 *정의로 이동(go-to-definition)* , *참조 찾기(find references)* , *호버 시 문서 표시* 같은 *IDE 수준의 코드 인텔리전스 기능* 제공
- /terminal-setup 명…
------------------------------
https://news.hada.io/topic?id=25269&utm_source=googlechat&utm_medium=bot&utm_campaign=1834
NextJS + Vercel AI SDK로 챗봇 UI 만들다가 수명이 줄겠네..
State of the Art가 별건가… 서령은 예술의 경지가 맞다. 입이 귀에 걸린 채로 먹고 온 것 같다. 잘 먹었습니다!!
오픈소스 프로젝트의 커뮤니티를 어떻게 운영해야 지속적으로 사람들이 참여하게 할 수 있을까..













