
Ncbi Pmc Skill
- 4 installs
- 4.9k repo stars
- Updated July 14, 2026
- openai/plugins
ncbi-pmc-skill skill documents Submit compact NCBI PMC Open Access requests for article/file availability metadata.
About
ncbi-pmc-skill skill documents Submit compact NCBI PMC Open Access requests for article/file availability metadata. Use when a user wants concise PMC Open Access summaries; save raw XML only on request.. name: ncbi-pmc-skill description: Submit compact NCBI PMC Open Access requests for article/file availability metadata. Use when a user wants concise PMC Open Access summaries; save raw XML only on request.
- Submit compact NCBI PMC Open Access requests for article/file availability metadata.
- Platform-specific setup patterns for ncbi-pmc-skill.
- Evidence-backed steps from upstream SKILL.md.
- When-to-use criteria for ncbi-pmc-skill versus alternatives.
Ncbi Pmc Skill by the numbers
- 4 all-time installs (skills.sh)
- Ranked #13,349 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
ncbi-pmc-skill capabilities & compatibility
- Capabilities
- ncbi pmc skill quick start · ncbi pmc skill when to use guidance · ncbi pmc skill integration patterns
- Use cases
- orchestration
What ncbi-pmc-skill says it does
Use `scripts/ncbi_pmc.py` for all PMC Open Access calls in this package.
This skill is intentionally narrow: it currently covers the PMC Open Access service rather than the full PMC API surface.
npx skills add https://github.com/openai/plugins --skill ncbi-pmc-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 4.9k |
| Last updated | July 14, 2026 |
| Repository | openai/plugins ↗ |
How do I use ncbi-pmc-skill correctly?
Submit compact NCBI PMC Open Access requests for article/file availability metadata. Use when a user wants concise PMC Open Access summaries; save raw XML only on request.
Who is it for?
Teams implementing ncbi-pmc-skill workflows from the catalog.
Skip if: Skip when requirements clearly match a different specialized stack.
When should I use this skill?
User asks about ncbi-pmc-skill, submit compact ncbi pmc open access requests for article/file availability metadata. use w.
What you get
Working ncbi-pmc-skill setup with validated configuration and next steps.
Files
Operating rules
- Use
scripts/ncbi_pmc.pyfor all PMC Open Access calls in this package. - This skill is intentionally narrow: it currently covers the PMC Open Access service rather than the full PMC API surface.
- Pass endpoint-specific query parameters under
params, typicallyidfor a PMCID or DOI-style lookup supported by the OA service. - Re-run requests in long conversations instead of relying on older tool output.
- Treat displayed
...in tool previews as UI truncation, not literal request content.
Execution behavior
- Return concise markdown summaries from the script output by default.
- Return raw XML only if the user explicitly asks for machine-readable output.
- Prefer targeted endpoint calls instead of broad unfiltered dumps.
- If the user needs the full raw response, set
save_raw=trueand report the saved file path.
Input
- Read one JSON object from stdin.
- Optional fields:
params,record_path,max_items,max_depth,timeout_sec,save_raw,raw_output_path - Common PMC Open Access patterns:
{"params":{"id":"PMC3257301"},"max_items":10}{"params":{"id":"10.1093/nar/gkr1184"},"max_items":10}
Output
- Success returns
ok,source, and a compactsummary. - Use
raw_output_pathwhensave_raw=true. - Failure returns
ok=falsewitherror.codeanderror.message.
Execution
echo '{"params":{"id":"PMC3257301"},"max_items":10}' | python scripts/ncbi_pmc.pyReferences
- No additional runtime references are required; keep the import package limited to this file and
scripts/ncbi_pmc.py.
interface:
display_name: "NCBI PMC"
short_description: "Fetch PMC Open Access summaries"
#!/usr/bin/env python3
"""Compact NCBI PMC Open Access helper for imported skills."""
from __future__ import annotations
import json
import os
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Any
try:
import requests
except ImportError as exc: # pragma: no cover
requests = None
REQUESTS_IMPORT_ERROR = exc
else:
REQUESTS_IMPORT_ERROR = None
PMC_OA_URL = "https://www.ncbi.nlm.nih.gov/pmc/utils/oa/oa.fcgi"
def error(code: str, message: str, warnings: list[str] | None = None) -> dict[str, Any]:
return {"ok": False, "error": {"code": code, "message": message}, "warnings": warnings or []}
def _require_str(name: str, value: Any, required: bool = False) -> str | None:
if value is None:
if required:
raise ValueError(f"`{name}` is required.")
return None
if not isinstance(value, str) or not value.strip():
raise ValueError(f"`{name}` must be a non-empty string.")
return value.strip()
def _require_int(name: str, value: Any, default: int) -> int:
if value is None:
return default
if not isinstance(value, int) or value <= 0:
raise ValueError(f"`{name}` must be a positive integer.")
return value
def _require_bool(name: str, value: Any, default: bool) -> bool:
if value is None:
return default
if not isinstance(value, bool):
raise ValueError(f"`{name}` must be a boolean.")
return value
def _require_object(name: str, value: Any) -> dict[str, Any]:
if value is None:
return {}
if not isinstance(value, dict):
raise ValueError(f"`{name}` must be an object.")
return value
def _get_by_path(value: Any, path: str) -> Any:
current = value
for part in path.split("."):
if isinstance(current, list):
if not part.isdigit():
raise ValueError(f"`record_path` segment {part!r} must be a list index.")
index = int(part)
if index >= len(current):
raise ValueError(f"`record_path` index {index} is out of range.")
current = current[index]
elif isinstance(current, dict):
if part not in current:
raise ValueError(f"`record_path` key {part!r} was not present in the response.")
current = current[part]
else:
raise ValueError(f"`record_path` segment {part!r} could not be applied.")
return current
def _infer_target(data: Any) -> tuple[str | None, Any]:
if isinstance(data, list):
return "$", data
if isinstance(data, dict):
for key in ("records", "items", "documents"):
value = data.get(key)
if isinstance(value, list):
return key, value
return None, data
def _compact(value: Any, max_items: int, max_depth: int) -> Any:
if isinstance(value, str):
return value if len(value) <= 240 else value[:240] + "..."
if max_depth <= 0:
if isinstance(value, (dict, list)):
return "..."
return value
if isinstance(value, list):
out = [_compact(item, max_items, max_depth - 1) for item in value[:max_items]]
if len(value) > max_items:
out.append(f"... (+{len(value) - max_items} more)")
return out
if isinstance(value, dict):
out: dict[str, Any] = {}
items = list(value.items())
for key, item in items[:max_items]:
out[str(key)] = _compact(item, max_items, max_depth - 1)
if len(items) > max_items:
out["_truncated_keys"] = len(items) - max_items
return out
return value
def _xml_to_simple(elem: ET.Element, max_items: int, max_depth: int) -> Any:
children = list(elem)
text = (elem.text or "").strip()
if not children:
return text
if max_depth <= 0:
return "..."
grouped: dict[str, Any] = {}
for child in children[:max_items]:
tag = child.tag.split("}", 1)[-1]
value = _xml_to_simple(child, max_items, max_depth - 1)
if tag in grouped:
current = grouped[tag]
if not isinstance(current, list):
grouped[tag] = [current]
grouped[tag].append(value)
else:
grouped[tag] = value
if len(children) > max_items:
grouped["_truncated_children"] = len(children) - max_items
if text:
grouped["_text"] = text
return grouped
def _save_raw_text(raw_text: str, raw_output_path: str | None, suffix: str) -> str:
path = Path(raw_output_path or f"/tmp/ncbi-pmc.{suffix}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(raw_text, encoding="utf-8")
return str(path)
def parse_input(payload: Any) -> dict[str, Any]:
if not isinstance(payload, dict):
raise ValueError("Input must be one JSON object.")
return {
"params": _require_object("params", payload.get("params")),
"record_path": _require_str("record_path", payload.get("record_path")),
"max_items": _require_int("max_items", payload.get("max_items"), 10),
"max_depth": _require_int("max_depth", payload.get("max_depth"), 3),
"timeout_sec": _require_int("timeout_sec", payload.get("timeout_sec"), 30),
"save_raw": _require_bool("save_raw", payload.get("save_raw"), False),
"raw_output_path": _require_str("raw_output_path", payload.get("raw_output_path")),
}
def _ncbi_common_params(params: dict[str, Any]) -> dict[str, Any]:
merged = dict(params)
api_key = os.environ.get("NCBI_API_KEY") or os.environ.get("NCBI_EUTILS_API_KEY")
tool = os.environ.get("NCBI_TOOL")
email = os.environ.get("NCBI_EMAIL")
if api_key and "api_key" not in merged:
merged["api_key"] = api_key
if tool and "tool" not in merged:
merged["tool"] = tool
if email and "email" not in merged:
merged["email"] = email
return merged
def _summary_output(
data: Any, config: dict[str, Any], raw_output_path: str | None
) -> dict[str, Any]:
record_path = config["record_path"]
path_used, target = (
_infer_target(data)
if record_path is None
else (record_path, _get_by_path(data, record_path))
)
output = {
"ok": True,
"source": "ncbi-pmc-oa",
"record_path": path_used,
"raw_output_path": raw_output_path,
"warnings": [],
}
if isinstance(target, list):
output.update(
{
"record_count_returned": min(len(target), config["max_items"]),
"record_count_available": len(target),
"truncated": len(target) > config["max_items"],
"records": _compact(
target[: config["max_items"]], config["max_items"], config["max_depth"]
),
}
)
else:
output["summary"] = _compact(target, config["max_items"], config["max_depth"])
if isinstance(target, dict):
output["top_keys"] = list(target)[: config["max_items"]]
return output
def execute(payload: Any) -> dict[str, Any]:
if requests is None:
return error("missing_dependency", f"`requests` is required: {REQUESTS_IMPORT_ERROR}")
config = parse_input(payload)
try:
response = requests.get(
PMC_OA_URL, params=_ncbi_common_params(config["params"]), timeout=config["timeout_sec"]
)
response.raise_for_status()
raw_output_path = (
_save_raw_text(response.text, config["raw_output_path"], "xml")
if config["save_raw"]
else None
)
root = ET.fromstring(response.text)
data = {
root.tag.split("}", 1)[-1]: _xml_to_simple(
root, config["max_items"], config["max_depth"]
)
}
return _summary_output(data, config, raw_output_path)
except ValueError as exc:
return error("invalid_response", str(exc))
except ET.ParseError as exc:
return error("invalid_response", f"Could not parse XML response: {exc}")
except requests.RequestException as exc:
return error("network_error", f"Request failed: {exc}")
def main() -> int:
try:
payload = json.load(sys.stdin)
except Exception as exc: # noqa: BLE001
sys.stdout.write(json.dumps(error("invalid_json", f"Could not parse JSON input: {exc}")))
return 2
try:
output = execute(payload)
except ValueError as exc:
output = error("invalid_input", str(exc))
code = 2
else:
code = 0 if output.get("ok") else 1
sys.stdout.write(json.dumps(output))
return code
if __name__ == "__main__":
raise SystemExit(main())
Related skills
FAQ
What does ncbi-pmc-skill do?
ncbi-pmc-skill skill documents Submit compact NCBI PMC Open Access requests for article/file availability metadata.
When should I use ncbi-pmc-skill?
User asks about ncbi-pmc-skill, submit compact ncbi pmc open access requests for article/file availability metadata. use w.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.