
A few days ago, I shared how I'm connecting Claude to UluP Spaces through MCP.
One design decision was intentionally restrictive: the AI could perform actions inside a Space without being given broad read access to the project's private content.
The idea was simple: minimal access by default.
Then someone here pointed out an obvious edge case.
If the model can't see that a node already exists, how can it know whether creating another one will produce a duplicate?
That turned out to be a much better test of the design than any theoretical discussion.
So I changed the implementation.
The MCP can now use the minimal structural information needed to check for an existing node before creating a new one. It doesn't need broad access to the full project content just to answer a structural question.
The line I'm currently experimenting with is:
Structural metadata when necessary. Actual project content only when explicitly needed.
That feels like a more practical interpretation of least privilege than simply making everything write-only.
The feedback also pushed me to separate the MCP integration into its own public repository:
https://github.com/Emanuele110706/ulup-spaces-mcp
The main UluP Spaces codebase remains private, but I wanted the MCP integration itself to be easier to inspect, document and eventually list in MCP directories.
I'm still figuring out where the boundary between useful context and unnecessary access should be, but this was a good reminder that real edge cases are better design tests than assumptions.
If you're building MCP tools, where do you draw the line between structural metadata and actual user content?
Splitting the MCP integration into its own repo is the right call; it also makes it much easier to keep your MCP-facing API surface stable, even when the core product changes underneath. One thing I'd add: if you want it listed in MCP directories, keep the README examples runnable end to end; most people copy-paste the first example and bounce if it doesn't work out of the box.
I really like the distinction between structural metadata and actual content. It feels like a better way to think about least privilege for MCP tools: don't ask for access to the content just because the tool needs enough context to make a safe decision.
The duplicate-node example is especially interesting because it shows that “minimal access” isn't always the same as “no read access.” Sometimes a small amount of structural context is actually what makes the write operation safer.
I’m curious how you’re thinking about this as the number of MCP actions grows. Do you see each tool declaring exactly which structural metadata it needs, almost like a fine-grained permission contract?
That's a really clean way to frame it — a permission contract per tool. Right now I don't have that formalized; get_project_overview does structural + a bit of content (pending task text) in one call, which in hindsight blurs the line I'm arguing for.
Your question is pushing me toward something like: a strict "structure-only" tier (ids, names, counts, relationships) that any new tool can request cheaply, separate from anything that touches actual content. Haven't built the contract layer yet, but this is making me think it should exist before the tool count grows much further, not after.
Speaking as someone who runs MCP servers (Claude Code) all day on the consumer side: your structural-metadata line is exactly right, and the duplicate-node edge case is the classic proof. Write-only tools feel safe on paper but push agents into blind-create behavior, which produces the messiest possible failure mode - duplicated state the agent then can't see to clean up. A practical pattern worth considering: keep a cheap 'list ids / exists?' tool strictly separate from any content-fetch tool, so the agent can answer structural questions without ever being tempted to pull content. Also +1 on splitting the MCP into its own repo - inspectability of the permission surface is half the trust decision when I add a third-party server.
The "exists?" vs "fetch content" separation is a really good articulation of something I was doing instinctively but hadn't named cleanly. My duplicate-check today is baked into create_node rather than being its own callable tool — which works, but means an agent can't ask "does this already exist?" without also being in the middle of a write. Splitting that into its own read-only existence-check tool is a much better shape, and cheap to add. And agreed on the repo split — I did the same (a separate ulup-spaces-mcp repo just for the integration surface, closed-source app behind it), and it's already paid off just in this thread — people can actually see what the tool boundaries are instead of taking my word for it.