팀 전체가 안전하게 공유하는 엔터프라이즈 Model Context Protocol 서버 구축 가이드: Streamable HTTP와 OAuth 2.1 실전 구현
팀 MCP 서버를 처음 배포했을 때, 인턴이 trigger_pipeline 도구를 무제한으로 호출할 수 있다는 사실을 뒤늦게 발견한 경험이 있습니다. 토큰 하나로 서버에 접근하면 모든 도구를 사용할 수 있었고, 누가 어떤 도구를 언제 호출했는지 기록조차 없었습니다. Claude 데스크톱 앱에서 로컬로 돌리는 MCP 서버는 만들기 쉽지만, 팀원 10명이 함께 쓰는 순간 이야기가 달라집니다. 누가 어떤 도구에 접근할 수 있는지, 자격 증명은 어떻게 관리하는지, 감사 로그는 남기고 있는지 등 엔터프라이즈 수준의 요구사항이 쏟아집니다.
TL;DR — 이 글을 읽고 나면 Streamable HTTP 기반 MCP 서버를 구축하고, OAuth 2.1 게이트웨이 패턴으로 팀 전체의 접근을 안전하게 제어하는 아키텍처를 설계할 수 있습니다. 다른 MCP 튜토리얼과 달리, 실제 보안 사고로 이어지는 코드 레벨 실수와 그 대응 패턴까지 함께 다룹니다.
이 글에서는 2025년 MCP 명세에 공식 채택된 Streamable HTTP 전송 방식으로 원격 MCP 서버를 구축하고, OAuth 2.1 인증을 적용해 팀 전체가 안전하게 공유하는 아키텍처를 단계별로 살펴봅니다. 대상 독자는 백엔드·풀스택 개발자로, JWT, OAuth, REST API에 대한 기본 이해가 있다고 가정합니다. TypeScript SDK 코드 예시와 함께 실제 운영 환경에서 고려해야 할 보안 패턴과 흔한 실수까지 담았습니다.
핵심 개념
MCP란 무엇인가
**Model Context Protocol(MCP)**은 Anthropic이 2024년 11월 오픈소스로 공개한 AI 통합 표준 프로토콜입니다. LLM이 파일 시스템, 데이터베이스, 외부 API 같은 자원에 접근할 때 사용하는 공통 인터페이스를 정의합니다. USB-C가 다양한 기기를 하나의 규격으로 연결하듯, MCP는 AI 에이전트와 외부 도구 사이의 표준 커넥터 역할을 합니다.
2025년 현재 Anthropic, OpenAI, Google, Microsoft 모두 MCP를 표준으로 수용했고, 월간 SDK 다운로드 수는 9,700만 회를 넘어섰습니다.
이 글에서의 MCP 전제 지식:
Tools(함수 호출),Resources(데이터 읽기),Prompts(재사용 가능한 프롬프트 템플릿)의 개념을 알고 있다면 충분합니다. MCP 자체가 처음이라면 공식 명세 소개 문서를 먼저 읽어보시는 것을 권장합니다.
Streamable HTTP: 왜 SSE를 버렸나
기존 MCP는 HTTP+SSE(Server-Sent Events) 방식을 사용했습니다. 클라이언트가 GET 엔드포인트로 SSE 연결을 열고, POST 엔드포인트로 요청을 보내는 구조였는데, 두 개의 엔드포인트를 항상 관리해야 하고 서버리스 환경에서는 장기 연결 유지가 어려웠습니다. 또한 SSE 연결이 많아지면 브라우저의 HTTP/1.1 동시 연결 수 제한(도메인당 6개)에 걸려 병목이 생기는 문제도 있었습니다.
2025년 3월 MCP 명세에 추가된 Streamable HTTP는 이 문제들을 해결합니다.
| 구분 | HTTP+SSE (구방식) | Streamable HTTP (신방식) |
|---|---|---|
| 엔드포인트 수 | GET + POST 두 개 | 단일 엔드포인트 |
| 스트리밍 방식 | 항상 SSE | 필요 시 SSE로 자동 업그레이드 |
| 서버리스 지원 | 제한적 | 온디맨드 처리 가능 |
| 세션 관리 | 복잡 | Mcp-Session-Id 헤더로 명시적 처리 |
| HTTP/1.1 연결 제한 | 별도 GET 연결 상시 유지 필요 | SSE 미사용 시 제한 없음 |
| 현재 상태 | Deprecated | 공식 권장 방식 |
Stateful vs Stateless: Streamable HTTP는 두 가지 운영 모드를 지원합니다.
Mcp-Session-Id헤더를 활용하면 세션을 유지하는 Stateful 모드로, 헤더 없이 각 요청을 독립적으로 처리하면 Stateless 모드로 동작합니다. 서버리스 배포에는 Stateless가 비용 효율적입니다.
OAuth 2.1 기반 MCP 인증 흐름
MCP 서버를 팀이 공유하려면 "누가 어떤 도구를 쓸 수 있는가"를 제어해야 합니다. MCP 명세 2025-11-25 버전(Authorization 명세)부터 원격 서버에 OAuth 2.1 기반 인증 프레임워크가 정식 포함되었습니다.
OAuth 주요 용어 정리
- JWT(JSON Web Token): 서명된 토큰. 서버가 IdP에 묻지 않고 로컬에서 내용을 검증할 수 있습니다.
- JWKS(JSON Web Key Set): IdP가 공개하는 공개 키 모음. JWT 서명 검증에 사용됩니다.
- Authorization Code Flow: 사용자가 IdP에 직접 로그인 후 인가 코드를 받아 토큰과 교환하는 흐름입니다.
- PKCE(Proof Key for Code Exchange): 인가 코드 탈취를 방지하는 보안 확장. OAuth 2.1에서 필수입니다.
전체 인증 흐름은 다음과 같습니다.
1. 클라이언트 → MCP 서버: 토큰 없이 요청
2. MCP 서버 → 클라이언트: 401 Unauthorized
+ WWW-Authenticate 헤더 (메타데이터 위치 안내)
3. 클라이언트 → IdP: PKCE 포함 Authorization Code Flow 시작
4. IdP → 클라이언트: 인가 코드 발급
5. 클라이언트 → IdP: 코드 + PKCE verifier로 액세스 토큰 요청
6. IdP → 클라이언트: 단기 액세스 토큰(JWT) 발급
7. 클라이언트 → MCP 서버: Bearer 토큰과 함께 재요청
8. MCP 서버: 토큰 검증
┌─ JWT 방식(권장): JWKS 공개 키로 서명을 로컬 검증 → IdP 추가 요청 없음
└─ Introspection 방식: IdP의 /introspect 엔드포인트에 검증 요청
9. MCP 서버 → 클라이언트: 정상 응답이 글의 코드 예시는 JWT + JWKS 방식을 사용합니다. 토큰이 도착할 때마다 IdP에 요청하지 않고 공개 키로 서명을 검증하기 때문에 레이턴시가 낮고 IdP 의존도가 줄어듭니다. Introspection 방식은 토큰 즉시 폐기(revocation)가 필요한 경우에 선택합니다.
PKCE(Proof Key for Code Exchange): 인가 코드를 탈취당해도 토큰을 발급받지 못하도록 하는 보안 확장입니다. OAuth 2.1에서는 모든 Authorization Code Flow에 PKCE가 필수입니다. 기존 OAuth 2.0의 암묵적 플로우(Implicit Flow)는 완전히 제거되었습니다.
실전 적용
1. TypeScript Streamable HTTP 서버 구현
MCP TypeScript SDK v1.10.0 이상에서 StreamableHTTPServerTransport를 사용해 원격 MCP 서버를 구축할 수 있습니다.
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "crypto";
import { z } from "zod";
const app = express();
app.use(express.json());
// 세션별 transport 인스턴스 관리 (Stateful 모드)
// ⚠️ 주의: 이 Map은 단일 프로세스 메모리에 저장됩니다.
// 서버 재시작 시 모든 세션이 유실됩니다.
// 프로덕션에서는 반드시 Redis 등 외부 저장소로 교체가 필요합니다.
const sessions = new Map<string, StreamableHTTPServerTransport>();
function createMcpServer() {
const server = new McpServer({
name: "enterprise-mcp-server",
version: "1.0.0",
});
server.tool(
"get_jira_issue",
"Jira 이슈 정보를 조회합니다",
{
issueKey: z.string().describe("Jira 이슈 키 (예: PROJ-123)"),
},
async ({ issueKey }) => {
// 실제 구현에서는 Jira REST API 호출
return {
content: [{ type: "text", text: `이슈 ${issueKey}: [API 응답 데이터]` }],
};
}
);
return server;
}
// 단일 엔드포인트로 GET/POST/DELETE 모두 처리
app.all("/mcp", async (req, res) => {
try {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && sessions.has(sessionId)) {
transport = sessions.get(sessionId)!;
} else if (!sessionId && req.method === "POST") {
const newSessionId = randomUUID();
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => newSessionId,
// 스트리밍이 불필요한 요청은 일반 JSON으로 응답해 리소스 절감
enableJsonResponse: true,
});
const server = createMcpServer();
await server.connect(transport);
sessions.set(newSessionId, transport);
// ⚠️ setTimeout은 프로세스 재시작 시 초기화됩니다.
// 슬라이딩 윈도우 방식이 필요하면 Redis TTL을 활용하세요.
setTimeout(() => sessions.delete(newSessionId), 30 * 60 * 1000);
} else {
res.status(400).json({ error: "Invalid session" });
return;
}
await transport.handleRequest(req, res);
} catch (err) {
console.error("MCP 요청 처리 오류:", err);
if (!res.headersSent) {
res.status(500).json({ error: "Internal server error" });
}
}
});
app.listen(3000, () => {
console.log("MCP 서버가 http://localhost:3000/mcp 에서 실행 중입니다");
});| 코드 포인트 | 설명 |
|---|---|
sessions Map |
인메모리 세션 관리. 단일 프로세스 한정이며 재시작 시 세션이 사라집니다. 프로덕션에서는 Redis 등 외부 저장소 교체 필요 |
app.all("/mcp", ...) |
단일 엔드포인트로 모든 HTTP 메서드 처리 — Streamable HTTP의 핵심 |
enableJsonResponse: true |
스트리밍 불필요 시 일반 JSON 반환으로 리소스 절감 |
try/catch |
transport 오류가 서버 전체를 중단시키지 않도록 보호 |
2. OAuth 2.1 게이트웨이 미들웨어 구현
개별 MCP 서버마다 OAuth 처리 코드를 심는 대신, 앞단에 MCP 게이트웨이를 두는 패턴이 엔터프라이즈 표준으로 자리잡고 있습니다.
[AI Client: Claude / Cursor]
│
▼
[MCP Gateway / Proxy] ← 토큰 검증, RBAC, 감사 로그, 속도 제한
│
├── JWT 서명 검증 → [JWKS: Okta / Azure AD / Keycloak]
│
▼
┌─────────────────────────────────┐
│ [MCP Server A: Jira Tools] │
│ [MCP Server B: DB Query Tools] │
│ [MCP Server C: CI/CD Tools] │
└─────────────────────────────────┘게이트웨이 레이어에서 토큰을 검증하는 Express 미들웨어 예시입니다.
import { Request, Response, NextFunction } from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";
// IdP의 JWKS 엔드포인트 — JWT 서명 검증에 필요한 공개 키를 동적으로 가져옵니다.
// 키 교체(rotation) 시 별도 배포 없이 자동 대응됩니다.
const JWKS = createRemoteJWKSet(
new URL("https://your-idp.example.com/.well-known/jwks.json")
);
// 도구별 필요 스코프 매핑 테이블
const TOOL_SCOPE_MAP: Record<string, string[]> = {
get_jira_issue: ["jira.read"],
create_jira_issue: ["jira.write"],
trigger_pipeline: ["cicd.trigger"], // 고위험 작업은 별도 스코프로 격리
query_database: ["db.read"],
};
export async function authMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
// RFC 9728: 인증 실패 시 메타데이터 위치를 클라이언트에게 안내
res.setHeader(
"WWW-Authenticate",
`Bearer realm="mcp", resource_metadata="https://mcp.yourcompany.com/.well-known/oauth-protected-resource"`
);
res.status(401).json({ error: "Authentication required" });
return;
}
try {
const token = authHeader.slice(7);
// JWT 서명을 JWKS 공개 키로 로컬 검증 — IdP에 추가 요청 없음
const { payload } = await jwtVerify(token, JWKS, {
audience: "https://mcp.yourcompany.com",
});
// MCP JSON-RPC 요청에서 도구명 추출
// tools/call 이외의 메서드(initialize, tools/list 등)는 null 반환
const requestedTool = extractToolName(req.body);
if (requestedTool !== null) {
// tools/call 요청: 도구별 스코프 검증
const requiredScopes = TOOL_SCOPE_MAP[requestedTool] ?? [];
const tokenScopes = ((payload.scope as string) ?? "").split(" ");
const hasPermission = requiredScopes.every((s) => tokenScopes.includes(s));
if (!hasPermission) {
await auditLog({
userId: payload.sub,
tool: requestedTool,
result: "DENIED",
requiredScopes,
tokenScopes,
timestamp: new Date(),
});
res.status(403).json({ error: "Insufficient scope", required: requiredScopes });
return;
}
}
// tools/call 이외의 요청(initialize 등)은 토큰 유효성만 확인 후 통과
(req as any).user = payload;
next();
} catch (err) {
res.status(401).json({ error: "Invalid or expired token" });
}
}
function extractToolName(body: any): string | null {
// tools/call 메서드에서만 도구명을 추출합니다.
// initialize, tools/list, resources/list 등 다른 메서드는 null 반환 →
// 호출자(authMiddleware)에서 null 여부를 확인해 스코프 검사를 건너뜁니다.
if (body?.method === "tools/call") {
return body?.params?.name ?? null;
}
return null;
}| 코드 포인트 | 설명 |
|---|---|
createRemoteJWKSet |
IdP의 공개 키를 동적으로 가져와 서명 검증 — 키 교체에 자동 대응 |
TOOL_SCOPE_MAP |
도구별 필요 스코프 정의 — cicd.trigger 같은 고위험 도구를 별도 스코프로 격리 |
extractToolName 반환 null |
initialize 등 비 tools/call 요청은 스코프 검사 없이 토큰 유효성만 확인 후 통과 |
WWW-Authenticate 헤더 |
RFC 9728 준수 — 클라이언트가 인증 서버를 자동 발견할 수 있게 안내 |
auditLog |
거부된 접근도 반드시 기록 — 보안 감사 및 이상 탐지에 필수 |
3. Cloudflare Workers 서버리스 배포
서버 운영 부담 없이 엣지에서 MCP 서버를 실행하려면 Cloudflare Workers가 좋은 선택입니다. Durable Objects로 세션 상태를 관리하고, Zero Trust로 SSO를 적용할 수 있습니다.
// worker.ts
// ⚠️ 아래 코드는 구조를 설명하는 의사 코드입니다.
// Cloudflare Agents SDK와 Vectorize API 시그니처는 변경될 수 있으므로
// 실제 구현 전 Cloudflare 공식 문서를 반드시 확인하세요.
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMcpAgent extends McpAgent {
server = new McpServer({ name: "cf-mcp-server", version: "1.0.0" });
async init() {
this.server.tool(
"search_docs",
"내부 문서를 검색합니다",
{ query: z.string() },
async ({ query }) => {
// Vectorize는 텍스트가 아닌 벡터 값을 직접 전달해야 합니다.
// 실제 구현: AI Workers로 임베딩 생성 후 Vectorize에 질의합니다.
const embedding = await this.env.AI.run(
"@cf/baai/bge-small-en-v1.5",
{ text: query }
);
const results = await this.env.VECTORIZE_INDEX.query(
embedding.data[0],
{ topK: 5 }
);
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
}
);
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/mcp") {
// Cloudflare Zero Trust가 앞단에서 SSO 처리
// 이미 인증된 요청만 이 핸들러에 도달합니다
return MyMcpAgent.serve("/mcp").fetch(request, env);
}
return new Response("Not found", { status: 404 });
},
};Cloudflare Zero Trust + MCP Server Portals (Open Beta): Cloudflare의 Access 정책을 MCP 서버 앞단에 적용하면 Okta, Azure AD, Google Workspace 등 기업 IdP와 연동한 SSO를 별도의 인증 코드 없이 구현할 수 있습니다. 게이트웨이 패턴을 플랫폼 레이어에서 해결하는 방식으로, 서버 운영 부담을 최소화하고 싶은 팀에 좋은 출발점이 됩니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 단순한 클라이언트 구현 | 단일 엔드포인트로 연결해 필요 시 스트리밍 자동 활용 |
| 서버리스 친화성 | AWS Lambda, Cloudflare Workers에서 온디맨드 실행 — 비용 효율적 |
| 기존 HTTP 인프라 재사용 | 로드밸런서, CDN, API Gateway와 자연스럽게 통합 |
| 검증된 보안 표준 | PKCE 필수, 암묵적 플로우 제거 — OAuth 2.1의 보안 강화 내용 반영 |
| 기업 IdP 통합 | Okta, Azure AD, Keycloak 등 기존 인프라 재활용 가능 |
| 세분화된 접근 제어 | logs.read, incidents.trigger 등 도구별 스코프로 최소 권한 원칙 적용 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 세션 관리 복잡도 | Mcp-Session-Id의 생성·저장·만료를 안전하게 처리해야 함 |
Redis 등 외부 저장소 사용, TTL 설정 필수 |
| HTTP/1.1 연결 제한 | SSE 연결이 많아지면 브라우저의 동시 연결 수 제한(도메인당 6개)에 걸릴 수 있음 | HTTP/2 또는 HTTP/3 사용, 연결 수 모니터링 |
| OAuth 구현 복잡도 | Dynamic Client Registration, Protected Resource Metadata 등 여러 RFC를 함께 구현해야 함 | 검증된 IdP(Okta, Keycloak)에 위임하고 직접 구현 최소화 |
| 대규모 트래픽 | MCP 커뮤니티에서 수백만 요청/일 규모의 운영 사례가 아직 드물며, 차세대 전송 방식 논의가 진행 중 | 공식 블로그 업데이트 추적, 아키텍처 주기적 검토 권장 |
| 의존성 보안 위험 | CVE-2025-6514처럼 타사 MCP 패키지에서 취약점 발생 | 의존성 감사 자동화(pnpm audit), 신뢰된 패키지만 사용 |
RFC 9728 (Protected Resource Metadata): MCP 서버가
/.well-known/oauth-protected-resource경로에 인증 서버 정보를 공개해, 클라이언트가 어디서 토큰을 발급받아야 하는지 자동으로 발견할 수 있게 하는 표준입니다.
실무에서 가장 흔한 실수
- 장기 정적 토큰 사용: API 키처럼 만료 없는 토큰을 발급해 사용하는 경우가 많습니다. OAuth 2.1의 핵심은 단기 토큰 + 자동 갱신인데, 이를 우회하면 자격 증명 탈취 시 피해가 장기화됩니다.
- PKCE 생략: "서버 간 통신이니까 괜찮겠지"라고 판단해 PKCE를 구현하지 않는 경우입니다. MCP 명세에서 PKCE는 선택이 아닌 필수이며, 생략 시 Authorization Code 탈취 공격에 노출됩니다.
- 도구 수준 접근 제어 부재: 토큰만 검증하고 "이 사용자가 이 도구를 쓸 수 있는가"를 확인하지 않는 경우입니다. 읽기 권한만 있는 사용자가
trigger_pipeline도구를 호출할 수 있는 상황이 발생합니다. 도구별 스코프 매핑(TOOL_SCOPE_MAP)을 반드시 구현하는 것을 권장합니다. - 평문 토큰 저장: 클라이언트 측에서 액세스 토큰을
localStorage나 환경 변수 파일에 평문으로 저장하는 경우입니다. 토큰은 암호화된 저장소나 OS 키체인을 활용하는 것이 안전합니다. - 감사 로그 미구현: 팀 공유 서버에서 누가 언제 어떤 도구를 사용했는지 기록하지 않으면, 보안 사고 발생 시 원인 추적이 불가능합니다. 거부된 접근 시도도 반드시 기록해두는 것을 권장합니다.
마치며
Streamable HTTP와 OAuth 2.1 게이트웨이 패턴을 조합하면, 개인 실험 수준의 MCP 서버를 팀 전체가 안전하게 공유할 수 있는 엔터프라이즈 인프라의 기본 보안 골격으로 격상시킬 수 있습니다. 모니터링, 속도 제한, 멀티테넌트 격리 등 추가 과제가 남아 있지만, 여기까지 구현하면 팀 공유를 시작할 수 있는 최소한의 안전한 기반이 마련됩니다.
지금 바로 시작해볼 수 있는 3단계:
- 로컬 MCP 서버 HTTP화:
pnpm add @modelcontextprotocol/sdk@latest로 SDK를 업데이트하고, 기존StdioServerTransport를StreamableHTTPServerTransport로 교체해볼 수 있습니다.app.all("/mcp", transport.handleRequest)처럼 단일 엔드포인트로 연결하는 것이 시작점입니다. - IdP 연동 시험: Keycloak을 Docker로 로컬에 띄운 뒤(
docker run -p 8080:8080 quay.io/keycloak/keycloak:latest start-dev), MCP 서버의 인증 흐름을 구성해볼 수 있습니다. PKCE 포함 Authorization Code Flow가 제대로 동작하는지 확인하는 것이 중요합니다. - 게이트웨이 미들웨어 추가 또는 Cloudflare 배포: 위 예시의
authMiddleware를 프로젝트에 추가하고TOOL_SCOPE_MAP에 팀의 도구별 스코프를 정의해볼 수 있습니다. 서버 운영 부담을 줄이고 싶다면 Cloudflare Workers + Zero Trust 조합으로 플랫폼 레이어에서 인증을 처리하는 방법도 좋은 선택입니다. 감사 로그까지 연결하면 기본 보안 골격이 완성됩니다.
다음 글: MCP 서버의 세션 격리와 멀티테넌트 데이터 유출 방지를 위한 Cloudflare Durable Objects 활용 패턴 심층 분석
참고 자료
핵심 자료 (시작점으로 권장)
- MCP 공식 명세 - Transports | modelcontextprotocol.io
- MCP 공식 명세 - Authorization (2025-11-25) | modelcontextprotocol.io
- MCP TypeScript SDK 공식 저장소 | github.com
- Keycloak 공식 문서 | keycloak.org
심화 자료
- MCP 공식 명세 - Authorization (2025-06-18) | modelcontextprotocol.io
- MCP Blog - MCP 전송 방식의 미래 | blog.modelcontextprotocol.io
- Cloudflare Blog - Streamable HTTP MCP 서버와 Python 지원 | blog.cloudflare.com
- Cloudflare Blog - Zero Trust MCP Server Portals | blog.cloudflare.com
- Cloudflare Blog - 원격 MCP 서버 구축 및 배포 | blog.cloudflare.com
- Microsoft ISE - OAuth 2.1 + Azure AD MCP 서버 구축 | devblogs.microsoft.com
- Spring.io - Spring AI MCP 서버 OAuth2 보안 적용 | spring.io
- Atlassian 원격 MCP 서버 공식 소개 | atlassian.com
- Scalekit - 엔터프라이즈 MCP Identity·SSO·인가 | scalekit.com
- WorkOS - 엔터프라이즈 레디 MCP 서버 만들기 | workos.com
- TrueFoundry - MCP 서버 보안 모범 사례 | truefoundry.com
- MCP OAuth 2.1, PKCE와 AI 인가의 미래 | aembit.io
- fka.dev - MCP이 SSE를 버리고 Streamable HTTP를 선택한 이유 | blog.fka.dev
- Koyeb - Streamable HTTP 원격 MCP 서버 배포 | koyeb.com
- MCP Authorization 명세 업데이트 (2026) | dasroot.net