toolkit-mcp-server
Generate IDs, QR codes, and hashes, encode values, geolocate IPs, plus gated host diagnostics.
사용해야 할까요
품질 및 안전성
도구 정의와 프로토콜 준수에 대한 자동 분석을 기반으로 합니다.
컨텍스트 비용
이는 서버의 도구가 모델의 컨텍스트에 로드될 때마다 소비되는 대략적인 토큰 수입니다. 수치가 높을수록 다른 작업에 사용할 수 있는 주의가 줄어듭니다.
설치
원클릭 설치
`claude_desktop_config.json` 파일에 다음을 추가하세요:
{
"mcpServers": {
"toolkit-mcp-server": {
"command": "bun",
"args": [
"@cyanheads/toolkit-mcp-server"
]
}
}
}실행 가능한 패키지
2.3.1streamable-http원격 엔드포인트
https://toolkit.caseyjhand.com/mcpstreamable-http할 수 있는 일
도구 목록
도구 (5)
🟢toolkit_hash_value(operation, value, algorithm, digestEncoding, expected, ...)
Generate a cryptographic digest of a value, or verify a value against an expected digest. Set operation to "generate" for a digest, or "compare" to constant-time-check value against the expected digest — compare is timing-safe and avoids manual string equality checks. Omitting operation compares when expected is supplied and generates otherwise. Algorithm defaults to sha256; sha384 and sha512 are also secure, while md5 and sha1 are exposed for checksum and file-integrity compatibility ONLY and must not be used for passwords, signatures, or any security purpose. digestEncoding selects the generated digest form: lowercase hex (default), base64, or sri (<algorithm>-<base64>, the npm lockfile integrity and Subresource Integrity form, sha256/sha384/sha512 only). expected is accepted as hex, base64, or SRI, recognized by its shape at the algorithm's digest length, so a published checksum can be pasted as-is; an SRI value may hold several space-separated entries, as an npm integrity field can, and matches when any entry for algorithm does. inputEncoding controls how value is read before hashing (utf8 default, or hex/base64 for raw binary data) so binary blobs need no decode round-trip. The canonical use is matching a download against a vendor-published checksum or a lockfile integrity entry.
입력 스키마
{
"type": "object",
"properties": {
"operation": {
"description": "\"generate\" produces a digest; \"compare\" constant-time-checks value against expected. When omitted, resolves to \"compare\" if expected is supplied and \"generate\" otherwise.",
"type": "string",
"enum": [
"generate",
"compare"
]
},
"value": {
"type": "string",
"description": "The data to hash, interpreted per inputEncoding (raw text by default)."
},
"algorithm": {
"default": "sha256",
"description": "Digest algorithm. sha256 (default), sha384, or sha512 for security; md5/sha1 are checksum/compat only — not for security.",
"type": "string",
"enum": [
"sha256",
"sha384",
"sha512",
"sha1",
"md5"
]
},
"digestEncoding": {
"default": "hex",
"description": "Form of the generated digest: lowercase hex (default), standard base64, or sri (<algorithm>-<base64>, sha256/sha384/sha512 only). Applies to operation \"generate\".",
"type": "string",
"enum": [
"hex",
"base64",
"sri"
]
},
"expected": {
"description": "The digest to compare against, as hex (any case), standard base64, or SRI (<algorithm>-<base64>); the form is recognized from its shape at the algorithm's digest length, and a string of only hex digits is always read as hex. An SRI value may carry several space-separated entries: entries for other algorithms are skipped, and it matches when any entry for algorithm matches. Supplying it with operation omitted runs a compare; it is rejected with operation \"generate\".",
"type": "string"
},
"inputEncoding": {
"default": "utf8",
"description": "How value is decoded before hashing: utf8 text, hex, or base64.",
"type": "string",
"enum": [
"utf8",
"hex",
"base64"
]
}
},
"required": [
"value"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}출력 스키마
{
"type": "object",
"properties": {
"algorithm": {
"type": "string",
"enum": [
"sha256",
"sha384",
"sha512",
"sha1",
"md5"
],
"description": "The algorithm used."
},
"operation": {
"type": "string",
"enum": [
"generate",
"compare"
],
"description": "The operation performed, after resolving an omitted operation."
},
"digest": {
"description": "Digest of value in the requested digestEncoding: lowercase hex, base64, or <algorithm>-<base64>. Present for operation \"generate\".",
"type": "string"
},
"matches": {
"description": "Constant-time equality of the computed digest against expected. Present for operation \"compare\".",
"type": "boolean"
},
"lengthInBytes": {
"description": "Digest size in bytes (32 for sha256, 48 for sha384, 64 for sha512, 20 for sha1, 16 for md5). Present for \"generate\".",
"type": "number"
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `missing_expected`: operation is \"compare\" but no expected digest was supplied. `expected_without_compare`: operation is \"generate\" but an expected digest was also supplied, so it would be ignored. `expected_malformed`: expected is not a hex, standard base64, or sha256/sha384/sha512 SRI digest, or an SRI value holds a token that is not an SRI entry. `expected_length_mismatch`: expected is a recognized digest form but its length does not match the algorithm, so compare would always fail. `expected_algorithm_mismatch`: expected is SRI and none of its entries names the chosen algorithm. `sri_unsupported_algorithm`: digestEncoding is \"sri\" and algorithm is md5 or sha1, which SRI does not define. `invalid_input_encoding`: value is not valid for the declared inputEncoding (e.g. non-hex characters with inputEncoding \"hex\"). Other values are possible when a failure originates below the handler.",
"examples": [
"missing_expected",
"expected_without_compare",
"expected_malformed",
"expected_length_mismatch",
"expected_algorithm_mismatch",
"sri_unsupported_algorithm",
"invalid_input_encoding"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"algorithm",
"operation"
]
},
{
"required": [
"error"
]
}
]
}🟢toolkit_generate_id(type, count)
Mint cryptographically-random identifiers using the platform CSPRNG — the correct source for IDs that must be unpredictable, unlike model-generated values. type selects the format: uuid_v4 (random, the default), uuid_v7 (time-ordered, sortable by creation), or ulid (26-char Crockford-base32, lexicographically sortable). Set count to mint a batch in one call (up to 1000); the returned ids array always contains exactly count values and is never truncated. For uuid_v7 and ulid, a batch is monotonic — strictly increasing even within the same millisecond — so the ids array stays in sorted creation order; ids minted in the same millisecond are separated by random gaps, so no id in a batch can be derived from another. IDs from this tool feed into toolkit_generate_qr (pass ids[0] as data) to create a scannable code.
입력 스키마
{
"type": "object",
"properties": {
"type": {
"default": "uuid_v4",
"description": "Identifier format: uuid_v4 (random), uuid_v7 (time-ordered), or ulid (sortable Crockford-base32).",
"type": "string",
"enum": [
"uuid_v4",
"uuid_v7",
"ulid"
]
},
"count": {
"default": 1,
"description": "How many identifiers to mint (1–1000). The full batch is always returned.",
"type": "integer",
"minimum": 1,
"maximum": 1000
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}출력 스키마
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"uuid_v4",
"uuid_v7",
"ulid"
],
"description": "The identifier format that was minted."
},
"ids": {
"type": "array",
"items": {
"type": "string",
"description": "A single minted identifier."
},
"description": "The minted identifiers — exactly count of them, in mint order; for uuid_v7 and ulid that order is strictly increasing (sorted by creation), with random gaps between ids minted in the same millisecond."
},
"count": {
"type": "number",
"description": "The number of identifiers minted (equals the requested count)."
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode."
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"type",
"ids",
"count"
]
},
{
"required": [
"error"
]
}
]
}🟢toolkit_generate_qr(data, format, errorCorrection, margin, scale)
Encode text or a URL into a QR code. data is the content to encode (a link, a generated identifier such as toolkit_generate_id's ids[0], or any string). format selects the output: svg returns inline SVG markup sized in pixels, png_base64 returns base64-encoded PNG bytes (with mimeType and byteLength), and terminal returns plain Unicode half-block characters (no escape codes) for a monospace display, drawn for a dark background: light modules, quiet zone included, are blocks and dark modules are spaces. errorCorrection (L/M/Q/H) trades data capacity for damage tolerance, margin sets the quiet-zone width in modules, and scale sets pixels per module for svg and png_base64, so both are (modules + 2 × margin) × scale pixels per side. The returned version (1–40) reflects how dense the encoded data is. png_base64 rejects an image past 2048 px per side with a typed raster_too_large error, so a dense symbol needs a lower scale; svg is vector markup and carries no such limit.
입력 스키마
{
"type": "object",
"properties": {
"data": {
"type": "string",
"minLength": 1,
"maxLength": 2953,
"description": "The text or URL to encode, stored as UTF-8. Capacity is counted in bytes: 2953 UTF-8 bytes is the absolute ceiling (QR version 40, level L, byte mode). The 2953-character limit here is only an upper bound, since a non-ASCII character takes 2–4 bytes. Usable capacity drops at higher errorCorrection levels, so over-capacity data is rejected with a typed data_too_large error rather than a generic failure."
},
"format": {
"default": "svg",
"description": "Output format: svg markup, png_base64 (raster bytes), or terminal (plain Unicode half-blocks, drawn for a dark background).",
"type": "string",
"enum": [
"svg",
"png_base64",
"terminal"
]
},
"errorCorrection": {
"default": "M",
"description": "Error-correction level: L (~7% recoverable) to H (~30%). Higher tolerance lowers data capacity.",
"type": "string",
"enum": [
"L",
"M",
"Q",
"H"
]
},
"margin": {
"default": 4,
"description": "Quiet-zone width in modules around the symbol. The spec recommends 4.",
"type": "integer",
"minimum": 0,
"maximum": 20
},
"scale": {
"default": 4,
"description": "Pixels per module for svg (its width and height) and png_base64. Ignored for terminal. png_base64 also bounds the whole image at 2048 px per side, so a dense symbol or a wide margin admits a lower scale than 32 there.",
"type": "integer",
"minimum": 1,
"maximum": 32
}
},
"required": [
"data"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}출력 스키마
{
"type": "object",
"properties": {
"format": {
"type": "string",
"enum": [
"svg",
"png_base64",
"terminal"
],
"description": "The format that was produced."
},
"content": {
"type": "string",
"description": "The QR artifact: SVG markup, the terminal half-block grid (newline-separated rows), or base64 PNG bytes for png_base64."
},
"mimeType": {
"description": "MIME type of content for image formats. Absent for the terminal format.",
"type": "string",
"enum": [
"image/svg+xml",
"image/png"
]
},
"byteLength": {
"description": "Decoded byte size of the PNG. Present only for png_base64.",
"type": "number"
},
"version": {
"type": "number",
"description": "QR symbol version (1–40); higher versions hold denser data and indicate denser content."
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `data_too_large`: data exceeds the QR capacity for the chosen errorCorrection level and encoding mode. `raster_too_large`: format is png_base64 and (modules + 2 × margin) × scale exceeds the pixel budget. Other values are possible when a failure originates below the handler.",
"examples": [
"data_too_large",
"raster_too_large"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"format",
"content",
"version"
]
},
{
"required": [
"error"
]
}
]
}🟢toolkit_encode_value(operation, encoding, value, outputEncoding)
Encode or decode a value across base64, base64url, hex, or URL (percent) encoding, in either direction. Set operation to "encode" to transform raw UTF-8 text into the chosen encoding, or "decode" to recover the original bytes from an encoded value. Decoded bytes come back as UTF-8 text by default; set outputEncoding to "hex" or "base64" to receive them re-encoded instead, which is lossless for binary data and transcodes between encodings (a base64 digest to hex, for example). Decoding never substitutes replacement characters: bytes that are not valid UTF-8 text are reported as a recoverable error that points at outputEncoding. Whitespace in hex, base64, and base64url values is ignored, so line-wrapped MIME bodies and PEM bodies decode as-is (drop PEM's -----BEGIN/END----- lines, which are not base64). base64url uses the URL-safe alphabet (- and _ instead of + and /); url applies encodeURIComponent and percent-decoding. A value that is malformed for the chosen encoding is reported as a recoverable error, not a silent best-effort.
입력 스키마
{
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": [
"encode",
"decode"
],
"description": "\"encode\" transforms text into the encoding; \"decode\" recovers the bytes from an encoded value."
},
"encoding": {
"type": "string",
"enum": [
"base64",
"base64url",
"hex",
"url"
],
"description": "The encoding to apply: base64, URL-safe base64url, hex, or URL percent-encoding."
},
"value": {
"type": "string",
"description": "The value to transform — raw text for encode, an encoded string for decode. Whitespace is ignored when decoding hex, base64, or base64url; a url value is taken literally."
},
"outputEncoding": {
"description": "Decode only: how the recovered bytes are returned. utf8 (used when omitted) returns text and fails when the bytes are not valid UTF-8; hex and base64 return the raw bytes re-encoded, losslessly. Rejected when operation is \"encode\".",
"type": "string",
"enum": [
"utf8",
"hex",
"base64"
]
}
},
"required": [
"operation",
"encoding",
"value"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}출력 스키마
{
"type": "object",
"properties": {
"encoding": {
"type": "string",
"enum": [
"base64",
"base64url",
"hex",
"url"
],
"description": "The encoding that was applied."
},
"operation": {
"type": "string",
"enum": [
"encode",
"decode"
],
"description": "The operation that was performed."
},
"outputEncoding": {
"description": "How result renders the decoded bytes: utf8 text, hex, or base64. Present for operation \"decode\".",
"type": "string",
"enum": [
"utf8",
"hex",
"base64"
]
},
"result": {
"type": "string",
"description": "The transformed value: encoded text for encode; for decode, the recovered bytes as UTF-8 text, hex, or base64 per outputEncoding."
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `decode_failed`: operation is \"decode\" but value is malformed for the chosen encoding. `decode_not_utf8`: operation is \"decode\", outputEncoding is utf8 (or omitted), and the decoded bytes are not valid UTF-8 text. `output_encoding_not_applicable`: operation is \"encode\" and outputEncoding was supplied; it selects how decoded bytes are returned. Other values are possible when a failure originates below the handler.",
"examples": [
"decode_failed",
"decode_not_utf8",
"output_encoding_not_applicable"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"encoding",
"operation",
"result"
]
},
{
"required": [
"error"
]
}
]
}🟢toolkit_geolocate_ip(target)
Resolve a public IP address (or hostname) to geographic and network metadata: country, region, city, latitude/longitude, the owning ASN and organization, timezone, and the proxy/hosting/mobile quality flags. target accepts an IPv4/IPv6 address or a hostname — a hostname is DNS-resolved first and the resolvedIp field echoes which IP was actually located. The provider is called directly (never the target), so this is SSRF-free and safe to expose anywhere. Results are best-effort and provider-bounded: VPNs, proxies, mobile NAT, and anycast all defeat IP-to-location, accuracy is city-level at best, and many fields can be absent for reserved or thinly-documented ranges — absent fields are reported as unknown, never invented. Read proxy, hosting, and mobile before trusting the coordinates: a true on any of them means the location describes infrastructure, not the user. Private/reserved addresses have no public geolocation and are rejected. The source field names which provider answered.
입력 스키마
{
"type": "object",
"properties": {
"target": {
"anyOf": [
{
"type": "string",
"format": "ipv4",
"pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$",
"description": "A raw IPv4 address."
},
{
"type": "string",
"format": "ipv6",
"pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$",
"description": "A raw IPv6 address."
},
{
"type": "string",
"pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$",
"description": "A dotted hostname, e.g. \"example.com\"."
}
],
"description": "A public IPv4/IPv6 address or a hostname (e.g. \"8.8.8.8\" or \"example.com\")."
}
},
"required": [
"target"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false
}출력 스키마
{
"type": "object",
"properties": {
"target": {
"type": "string",
"description": "The target as supplied (IP or hostname)."
},
"resolvedIp": {
"type": "string",
"description": "The IP that was actually located (a supplied hostname is resolved to this first)."
},
"country": {
"description": "Country name. Absent when the provider does not report it.",
"type": "string",
"maxLength": 256
},
"countryCode": {
"description": "ISO 3166-1 alpha-2 country code. Absent when unknown.",
"type": "string",
"maxLength": 256
},
"region": {
"description": "Region or state name. Absent when unknown.",
"type": "string",
"maxLength": 256
},
"city": {
"description": "City name. Absent when unknown.",
"type": "string",
"maxLength": 256
},
"latitude": {
"description": "Latitude in decimal degrees. Absent when unknown.",
"type": "number"
},
"longitude": {
"description": "Longitude in decimal degrees. Absent when unknown.",
"type": "number"
},
"asn": {
"description": "Autonomous System number, e.g. \"AS15169\". Absent on providers that omit it.",
"type": "string",
"maxLength": 256
},
"org": {
"description": "Owning organization or ISP, e.g. \"Google LLC\". Absent when unknown.",
"type": "string",
"maxLength": 256
},
"timezone": {
"description": "IANA timezone, e.g. \"America/Los_Angeles\". Absent when unknown.",
"type": "string",
"maxLength": 256
},
"proxy": {
"description": "True when the address is a known proxy, VPN, or Tor exit — the location describes the exit node, not the user. Absent when the provider does not report it.",
"type": "boolean"
},
"hosting": {
"description": "True when the address belongs to a hosting or datacenter network, so the location is a facility rather than a person. Absent when unreported.",
"type": "boolean"
},
"mobile": {
"description": "True when the address belongs to a mobile carrier network, where NAT can place the location far from the device. Absent when unreported.",
"type": "boolean"
},
"source": {
"type": "string",
"description": "The provider that answered the lookup, e.g. \"ip-api\"."
},
"error": {
"description": "Present when the call failed. Absent on success.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "JSON-RPC error code for this failure."
},
"message": {
"type": "string",
"description": "Human-readable description of what went wrong."
},
"data": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "Machine-readable failure mode. Declared by this tool: `unresolvable_host`: A hostname target failed DNS resolution. `private_target`: The target resolves to a private/reserved IP with no public geolocation. Other values are possible when a failure originates below the handler.",
"examples": [
"unresolvable_host",
"private_target"
]
},
"recovery": {
"description": "Actionable next step for the caller.",
"type": "object",
"properties": {
"hint": {
"type": "string"
}
},
"required": [
"hint"
],
"additionalProperties": {}
},
"retryable": {
"description": "Whether retrying may succeed.",
"type": "boolean"
}
},
"additionalProperties": {}
}
},
"required": [
"code",
"message"
],
"additionalProperties": {}
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"anyOf": [
{
"not": {
"required": [
"error"
]
},
"required": [
"target",
"resolvedIp",
"source"
]
},
{
"required": [
"error"
]
}
]
}커뮤니티
증거