JIT
← Notebook
Field notes AI AgentsMCPSecurity

Skills, MCP, and where the credentials belong

A simple question — do our agents need MCP? — that quietly turned into a clearer picture of skills, MCP servers, and the identity layer sitting underneath both.

We run a small fleet of AI agents in the homelab — systems, network, security, identity, endpoint. Each one is a thin shell around a set of skills, where a skill is a tool definition plus the code that calls a real backend. This week’s work didn’t add a skill. It started with a question about plumbing and ended somewhere more useful: a clear rule for when to build, when to buy, and where secrets are actually supposed to live.

The original question

The Model Context Protocol (MCP) is everywhere now. So the honest question came up: do our hand-built skills resemble MCP servers — or do we need to start bolting MCP onto our agents?

It sounds like a packaging question. It turned out to be three questions wearing one coat.

What a skill actually is

A skill in our framework is deliberately not an MCP server. It’s two things: a TOOLS list — each entry a name, a description, and a JSON Schema for its inputs — and a dispatch(tool_name, inputs) method that routes the call to a client. That’s it. No transport, no protocol assumptions.

That neutrality is the whole point. The same skill object feeds two different front-ends. One is the chat agent (which needs an Anthropic API key). The other is an MCP stdio server, built once in our shared framework, that loops over each skill’s tools and forwards calls straight into dispatch().

So the first answer landed before we’d written a line of code: our agents already are MCP servers. Every skill is already exposed over MCP, under the canonical mcp__<agent>__<skill>__<tool> naming, callable from Claude Code today. The schema shape we’d been writing for the chat path was MCP-compatible all along. There was nothing to add.

The follow-up: should we use MCP inside our logic?

The next instinct is to reach further: if MCP is the standard, should the agents speak it internally too? Three ways to read that, and all three are a no for us:

  • Make the chat-loop talk to skills over MCP. Today that’s an in-process function call. Routing it through MCP would add a subprocess and a serialization hop to replace a method call that already works. Pure overhead.
  • Rewrite skills as wrappers around third-party MCP servers. Our clients are ~100 lines of standard library against a vendor API — pinned, testable offline, CVE-scanned under our own dependency policy. Most backend MCP servers are young and churny; they’d fail “≥4 weeks old, pinned, CVE-clear” on day one. We’d be importing exactly the risk our policy exists to keep out.
  • Have agents call each other over MCP. Tempting — the endpoint agent flags an outdated package, the security agent has the CVE data. But we already have two cleaner answers: shared code belongs in a shared client library, and cross-agent orchestration already happens one layer up. Claude Code holds every agent’s MCP tools at once. It is the orchestrator. Building agent-to-agent plumbing would duplicate that and couple agents that should stay independent.

MCP earned its place as our egress — the way the outside world calls in. Pulling it inward buys nothing.

The question that actually mattered: where do the secrets live?

Then the real one surfaced. Today every skill reads a long-lived API key from a .env file. We want to kill that: have each agent check a credential out of a PAM service just-in-time — read-only or read-write, scoped, with automatic or human approval, and every session logged in detail.

So: would replacing a skill with an MCP server give us that?

No — and seeing why is the valuable part. PAM check-out, RO/RW scoping, approval, and audit are properties of how the credential is brokered. That is orthogonal to whether the tool is a skill or an MCP server. An MCP server needs a credential to reach its backend exactly like a skill does — and by default it gets a static secret in a config file, which is the very thing we’re trying to remove, relocated.

The skill-vs-MCP axis is simply the wrong axis for this. The right one is where the credential comes from and where approval and audit happen:

  • No static secret → the skill’s config loader stops reading .env and instead calls a broker that does a JIT check-out, returning a short-lived, scoped lease.
  • RO vs RW → PAM issues a different lease per scope; read tools request a read lease, write tools a write one.
  • Approval → either PAM’s own workflow, or our existing tool-layer safety guard (kill-switch, two-phase confirm, blast-radius gate), or both.
  • Audit → PAM logs every check-out; our dispatch layer logs the action plus the lease id, so a session ties back to a lease ties back to a backend change.

All of that lives in one broker in the shared framework. Wire it into the skill layer once and the MCP path inherits it for free — because the MCP servers load the same skills. You never wire it twice.

To be fair to MCP, there is one genuine benefit: a separate server process keeps the secret out of the model-driven process entirely — the client only ever sees tool results, never the key. If your threat model is the model itself being coerced into leaking a credential, that boundary helps. But short-lived, scoped, revocable PAM leases neutralise most of that anyway: a leaked lease is a contained incident, not a breach. The strong posture combines both, and you can put the boundary at a broker subprocess just as well as at an MCP server.

And the gotcha that decides whether any of this is actually more secure: you’ve shifted secret-zero. The broker now needs a credential to authenticate to PAM. If that becomes another static token in .env, you’ve moved the problem, not solved it. It has to be a host-bound machine identity — AppRole, mTLS, TPM, workload identity — not a stored token. And for logs worth keeping, thread the human operator’s identity through too, so the audit reads “Jitser, via the endpoint agent, checked out a 10-minute RW lease” — not “a machine did something.”

The conclusion

Three rules came out of one question:

  1. Build a skill by default. Adopt an external MCP server only when it clears the bar — first-party and policy-clean (pinned, ≥4 weeks old, CVE-clear), and you don’t also need that backend in the chat path. “A maintained MCP exists” makes it a candidate, not a default. For something like Microsoft Graph we’d still likely prefer a shared in-process client, because two agents need it and we want it under our own pin-and-audit control.

  2. Keep MCP as egress, not as internal plumbing. Our agents are already MCP servers; that’s where the protocol belongs. Cross-agent work happens at the orchestrator above them, not in wiring between them.

  3. The identity layer is a separate, orthogonal layer that links to either. Credential brokering sits below both skills and MCP and doesn’t care which is calling. Build it once against the skill layer, and the MCP servers get PAM check-out, scoping, approval, and per-session audit at no extra cost.

The takeaway

The question we started with — “do we need MCP?” — was the wrong one, and answering it honestly led to the right one: where do the credentials live? Packaging and identity are different layers, and conflating them is how you end up “modernising” by moving a static secret from one file to another. Decide them separately. The protocol is how the world talks to your agents; the broker is how your agents earn the right to act. Keep those apart and both get simpler.