지난 글에서는 Codex CLI를 API 뒤에 두고 Web, n8n, Salesforce 같은 여러 Caller가 공통으로 사용할 수 있는 Agent Runtime을 만들었다. 목표는 Coding Agent를 멋지게 자동화하는 것이 아니라, 현업에서 나온 아이디어가 실제 동작으로 돌아오기까지의 Feedback Latency를 줄이는 것이었다.
Runtime이 생기자 다음 단계는 자연스러웠다. Salesforce, Database, Search, 외부 API 같은 기능을 Tool로 연결하면 Agent가 필요한 일을 스스로 선택해 수행할 수 있다. MCP(Model Context Protocol)를 사용하면 이런 capability를 표준적인 방식으로 노출하는 것도 훨씬 쉬워진다.
그런데 실제 업무 시스템에 연결하려고 하니 질문이 하나 더 생겼다.
Agent가 Tool을 선택할 수 있다면, 실제 실행 권한까지 Agent에게 넘기는 것이 맞을까?
내가 내린 결론은 반대에 가까웠다.
Agent가 결정해야 하는 것은 ‘무엇을 할 것인가’이고, Application과 Runtime이 결정해야 하는 것은 ‘그것을 실제로 해도 되는가’다.
MCP가 연결을 표준화할수록 이 경계는 덜 중요해지는 것이 아니라 오히려 더 중요해진다.
MCP를 붙이면 Tool 연결은 쉬워진다
Agent가 외부 시스템과 일하려면 결국 capability가 필요하다. 고객 정보를 조회하거나, 문서를 검색하거나, 계산을 수행하거나, 특정 업무 상태를 변경해야 한다.
MCP는 이런 Tool, Resource, Prompt를 AI Application과 연결하는 공통 계약을 제공한다. 최근의 MCP 2026-07-28 Specification은 stateless protocol core, header-based routing, authorization hardening과 extension framework까지 확장하면서 production 환경에서 다루기 쉬운 방향으로 진화했다.
이 변화는 분명 반갑다. 서로 다른 Agent마다 같은 API Wrapper를 다시 만들 필요가 줄고, Tool의 입력과 출력 계약을 재사용할 수 있으며, Gateway나 Policy Layer와 결합하기도 쉬워진다.
하지만 여기서 한 가지를 분리해서 봐야 한다.
MCP
= Agent와 Capability 사이의 표준화된 Interface
Execution Authority
= 누가 어떤 Context와 권한으로 실제 Action을 수행할 수 있는지에 대한 결정
둘은 연결되어 있지만 같은 문제는 아니다.
MCP가 OAuth/OIDC 기반 Authorization을 강화한다고 해서 특정 영업사원이 특정 Opportunity의 Stage를 바꿔도 되는지, 계약 금액을 수정하려면 승인이 필요한지, 같은 요청이 Retry됐을 때 다시 실행해도 되는지까지 Protocol이 대신 결정해주는 것은 아니다.
그 영역은 여전히 Application과 업무 시스템의 책임이다.

‘호출할 수 있다’와 ‘호출해도 된다’는 다른 문제다
예를 들어 Agent에게 다음과 같은 Tool이 있다고 해보자.
update_opportunity
사용자가 “이 딜은 협상 단계로 바꿔줘”라고 요청했고 Agent가 다음 Argument를 만들었다.
{
"opportunity_id": "006...",
"stage": "Negotiation"
}
Tool 이름도 맞고 Parameter도 정상이다. LLM 입장에서는 충분히 실행 가능한 요청이다.
하지만 Enterprise Application 입장에서는 아직 확인할 것이 많다.
| 확인할 질문 | Source of Truth |
|---|---|
| 현재 사용자는 누구인가 | Application / Identity Provider |
| 이 Opportunity를 볼 수 있는가 | Salesforce / Authorization |
| Stage를 변경할 권한이 있는가 | Salesforce / Business Policy |
| 현재 화면에서 선택한 Record가 맞는가 | Application |
| 변경 전에 승인이 필요한가 | Business Policy |
| 같은 요청을 Retry해도 안전한가 | Runtime / Tool Contract |
| 변경 결과를 어디에 남길 것인가 | Runtime / Audit Layer |
이 값들은 대부분 LLM이 더 똑똑해지면 해결되는 종류의 문제가 아니다.
KEY TAKEAWAY. LLM에게 부족한 것은 Intelligence가 아니라 Authority다.
Agent가 “이 Tool을 쓰는 것이 적절하다”고 판단하는 것과, 실제 시스템이 “이 사용자가 이 Action을 실행할 수 있다”고 판단하는 것은 서로 다른 책임이다.
그래서 나는 Tool Call을 곧바로 실행 명령으로 보는 대신 Execution Proposal에 가깝게 보는 편이 안전하고 확장하기 쉽다고 생각하게 됐다.
Tool Planning과 Tool Execution을 분리한다
이 관점에서 Agent의 역할은 줄어드는 것이 아니다. 오히려 Agent가 잘하는 영역을 더 명확히 열어준다.
Agent는 사용자의 Intent를 해석하고, 어떤 정보가 필요한지 판단하고, 적절한 Tool을 선택하고, 여러 Tool을 어떤 순서로 사용할지 계획할 수 있다. 결과가 부족하면 추가 정보를 찾고, 여러 Evidence를 종합해 다음 Action을 제안할 수도 있다.
다만 Agent가 만든 Tool Intent와 Argument는 바로 외부 시스템으로 보내지 않는다.
User Request
↓
Application
↓
Agent Runtime
↓
LLM Reasoning / Planning
↓
Tool Intent + Proposed Arguments
↓
──────── Execution Boundary ────────
↓
Trusted Context Binding
Authorization
Policy Validation
Approval / Idempotency Check
↓
Deterministic Executor
↓
Salesforce / Azure / DB / API
↓
Evidence / Result
↓
Agent
여기서 Agent는 Reasoning Plane을 담당하고, Runtime은 Control Plane을 담당한다.
Agent가 “무엇을 해야 하는지”를 판단하는 동안 Runtime은 “이 요청이 현재 Context에서 실행 가능한지”를 결정한다. 실제 API 호출 역시 검증된 계약을 기준으로 deterministic하게 수행한다.
이렇게 분리하면 Agent의 자율성을 줄이지 않고도 실행 범위를 안정적으로 관리할 수 있다.

Runtime Context는 Agent가 추론할 대상이 아니다
이 구조를 만들면서 특히 중요하게 본 것이 Runtime Context였다.
예를 들어 Salesforce 화면에서 사용자가 특정 Opportunity를 보고 있다고 하자. Application은 이미 로그인한 User와 현재 Record를 알고 있다. 사용자의 Role이나 Tenant, 현재 Workspace도 Application 또는 Backend에서 확인할 수 있다.
그런데 이 값을 다시 Agent에게 찾아보라고 하는 것은 이상하다.
{
"input": "이 딜에서 지금 가장 먼저 확인해야 할 리스크가 뭐야?",
"runtime_context": {
"user_id": "005...",
"record_id": "006...",
"workspace_id": "deal-123",
"allowed_scope": [
"opportunity",
"account",
"proposal"
]
}
}
여기서 두 영역을 구분한다.
input
= Agent가 해석하고 추론할 대상
runtime_context
= Application이 확정해서 전달하는 사실
Identity, Tenant, Record Scope, Authorization Scope 같은 값은 Agent가 사용할 수는 있어도 임의로 확장해서는 안 된다.
예를 들어 Agent가 더 많은 정보가 필요하다고 판단할 수는 있다. 하지만 현재 allowed_scope가 opportunity, account, proposal이라면 Agent가 스스로 “이번에는 contract 데이터도 보자”고 Scope를 넓히는 구조는 만들지 않는다.
Agent는 Boundary 안에서 선택한다. Boundary 자체는 Application과 Runtime이 소유한다.
Tool은 API보다 업무 의미에 가깝게 설계한다
Execution Boundary를 분리하면 Tool의 Grain도 다시 보게 된다.
처음에는 기존 API나 SDK 함수를 그대로 Tool로 노출하기 쉽다.
run_soql()
get_record()
vector_search()
get_blob()
read_document()
이렇게 하면 Tool 하나하나는 단순하다. 대신 Agent가 Infrastructure Primitive를 조합해 Application Logic까지 수행해야 한다.
예를 들어 고객과 딜에 관련된 근거를 찾기 위해 Agent가 매번 Salesforce에서 Account ID를 찾고, 문서 목록을 조회하고, Search Index를 선택하고, 검색 결과를 다시 필터링해야 한다면 Tool은 원자적이지만 Agent의 Planning 부담은 커진다.
업무적으로는 다음과 같은 capability가 더 자연스러울 수 있다.
search_deal_evidence()
retrieve_customer_context()
prepare_next_action_context()
나는 이런 Tool을 Agentic하지 않지만 Business-semantic한 단위로 보는 편이 좋다고 생각한다.
Tool 내부에서는 인증된 Context와 정해진 Query Rule, Search 범위, Validation을 deterministic하게 처리한다. Agent는 어떤 업무 capability가 필요한지 판단하는 데 집중한다.
물론 반대쪽 극단도 피해야 한다.
analyze_everything_and_decide_next_action()
처럼 Tool 하나 안에 다시 불투명한 Agent Workflow를 숨기면 Boundary가 사라진다. Tool이 너무 작으면 Agent가 Application Logic을 떠안고, 너무 크면 Tool 내부의 판단 과정을 통제하기 어려워진다.
좋은 Grain은 Agent가 업무 의미로 선택할 수 있으면서도, 실행 결과는 예측 가능한 단위에 가깝다.
MCP는 없어지는 것이 아니라 역할이 더 선명해진다
이 설계는 MCP를 우회하자는 이야기가 아니다.
오히려 MCP의 역할을 더 명확하게 사용하자는 쪽에 가깝다.
MCP / Tool Contract
= Capability Interface
Agent
= Reasoning / Planning
Application
= Identity / Business Context
Runtime
= Policy / Validation / Control
Executor
= Actual Action
MCP의 Tool 정의에는 Input/Output Schema뿐 아니라 Tool의 성격을 나타내는 Annotation도 있다. readOnlyHint, destructiveHint, idempotentHint, openWorldHint 같은 정보는 Client가 Tool의 위험과 실행 특성을 이해하는 데 유용하다.
하지만 MCP Tool Annotations 문서가 강조하듯 이런 Annotation은 어디까지나 hint다. 신뢰할 수 없는 Server가 제공한 Annotation만으로 Tool 사용 여부를 결정해서는 안 된다.
이 구분이 중요하다.
Tool Metadata
→ Planning과 UX에 활용하는 정보
Execution Policy
→ 실제 Action을 허용하거나 거부하는 강제 규칙
최신 MCP가 Authorization을 강화한 것도 같은 맥락에서 볼 수 있다. Protocol-level Authorization은 Client와 Server가 신뢰 가능한 방식으로 연결되는 데 중요하다. 하지만 특정 사용자가 특정 Business Record에 어떤 Action을 수행할 수 있는지는 결국 해당 Application과 업무 시스템의 Policy가 판단해야 한다.
CAVEAT. Authentication과 Authorization이 있다고 해서 Business Execution Policy까지 자동으로 생기는 것은 아니다.
Read와 Write는 같은 Tool Policy를 가질 필요가 없다
모든 Tool을 같은 위험도로 다루는 것도 비효율적이다.
내가 실무적으로는 다음과 같이 구분하는 편이 이해하기 쉽다.
| 성격 | 예시 | 기본 Control |
|---|---|---|
| Read | 고객 Context, 문서, 검색 결과 조회 | 허용 Scope 안에서 Agent 자율 호출 |
| Assist | Next Action 후보, 요약, 분석 생성 | Agent 자율 실행 + 결과 검증 |
| Mutation | Opportunity Stage, 업무 상태 변경 | Policy Validation + 필요 시 승인 |
| High-impact | 계약, 금액, 외부 전송, 민감 Action | 강한 승인 + Audit + 제한된 Executor |
이 표는 MCP Annotation 자체를 대체하는 표준이 아니다. Application-level Governance를 설계하기 위한 실무적 분류다.
MCP의 readOnlyHint, destructiveHint, idempotentHint 같은 Metadata는 이런 Policy를 결정할 때 좋은 입력이 될 수 있다. 그러나 최종 Enforcement는 Runtime의 명시적인 규칙으로 남겨야 한다.
그래야 Tool이 추가되거나 Agent가 바뀌어도 Execution Boundary가 쉽게 흔들리지 않는다.
Agent에게 자유를 주되 Boundary 안에서 자유롭게 한다
Agentic Architecture를 설계하다 보면 자연스럽게 Agent에게 더 많은 Tool과 Context, 더 많은 자율성을 주고 싶어진다.
하지만 Enterprise 환경에서 목표는 단순히 Agent의 자유도를 최대화하는 것이 아니다.
Agent에게 열어둘 판단은 꽤 많다.
- 어떤 정보가 필요한가
- 어떤 Tool을 먼저 사용할 것인가
- 여러 Tool을 병렬 또는 순차로 사용할 것인가
- 현재 Evidence가 충분한가
- 추가 검색이 필요한가
- 결과를 어떻게 종합할 것인가
반대로 Agent가 스스로 바꾸지 않아야 하는 것도 명확하다.
- 나는 누구인가
- 어느 Tenant와 Customer Context에 속하는가
- 현재 어떤 Record를 기준으로 일하는가
- 어디까지 접근할 수 있는가
- 어떤 Action을 수정할 권한이 있는가
- 승인 없이 실행 가능한 범위는 어디까지인가
이 둘을 분리하면 Agent의 Reasoning은 충분히 Agentic하게 유지하면서도 실제 업무 시스템의 실행은 통제할 수 있다.

지난 글에서는 공통 Agent Runtime을 만들면서 새로운 PoC를 매번 다시 개발하는 문제를 조립하는 문제로 바꾸려고 했다.
이번에는 그 Runtime 안에서도 같은 원칙을 적용했다.
LLM이 잘하는 판단까지 Application 코드로 고정할 필요는 없다. 반대로 Application이 이미 알고 있고 책임져야 하는 Identity, Context, Permission을 LLM에게 다시 추론시키는 것도 불필요하다.
내가 원하는 구조는 결국 단순하다.
Agent
= Reasoning
MCP / Tool Contract
= Capability Interface
Application
= Context
Runtime
= Control
Executor
= Action
Agent가 더 많은 일을 할 수 있게 만드는 것보다 먼저 해야 할 일은 어디까지 맡겨도 되는지를 명확하게 만드는 것이다.
좋은 Agentic Architecture는 Agent에게 모든 것을 맡기는 구조가 아니라, 무엇을 맡겨도 되는지가 명확한 구조에 가깝다.