Direct answer

Design an MCP memory server around a small tool contract, stable paths and permissions checked on every operation. Resolve personal and workspace scope from authenticated identity, keep reading separate from writing, and bound how much recalled text reaches the agent. Treat revisions, provenance and retry behavior as part of the memory API so multiple agents can use it without silently overwriting each other.

Give each tool one memory operation

Start with six operations: list paths, read a record, search records, replace content, delete content and merge a remembered fact. Keep listing cheap: return titles, paths and revisions rather than every memory body. Give search a different contract from reading so an agent can discover a deployment decision before fetching its full explanation.

MCP's tools specification supplies discovery, invocation and JSON Schema contracts. The following is a proposed tool definition, with application-defined names and parameters; it does not document SPYN3's parameter schema.

{
  "name": "context_search",
  "description": "Find recorded memories in the caller's authorized scope.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "minLength": 1 },
      "scope": { "type": "string", "enum": ["personal", "workspace"] },
      "limit": { "type": "integer", "minimum": 1, "maximum": 8 }
    },
    "required": ["query", "scope"],
    "additionalProperties": false
  }
}

Return paths, excerpts, revisions and timestamps in a documented output schema. MCP supports structuredContent; its specification recommends also returning serialized JSON in a text block for backward compatibility. Enforce the result limit in code even when the client validates schemas. Keep tool descriptions specific enough that the agent chooses search before replacement.

Make paths readable without making them authority

A useful proposed layout is memory://me/memories/preferences.md, memory://workspace/memories/deployment.md and memory://workspace/skills/release/SKILL.md. Separate memories, skills and notes by kind: recalling a recorded preference and loading an executable procedure should be distinct client decisions.

MCP resources identify context with URIs and permit custom URI schemes. You can expose the same records through resources for hosts that offer resource selection, while retaining tools for model-directed search. Supporting resources does not require treating their URIs as public web addresses. See choosing resources, prompts or tools for context for that interface decision.

Resolve me to the authenticated person on the server. An unattended CI identity should have no personal namespace unless explicitly assigned one. Never accept a caller-supplied owner identifier as proof of ownership. Normalize paths once, reject traversal and ambiguous encodings, and authorize the resolved record.

Store an immutable record identifier alongside workspace, owner, audience, kind, path and revision. A rename changes the path; it should not erase provenance or create a second identity for the same deployment decision.

Authorize discovery, search and mutation separately

MCP's HTTP authorization specification describes token-based access and requires validation that a token was issued for the receiving server. Your application still needs to decide which person's memories that token can read. Define separate grants for reading, personal writes, shared writes and deletion; those are proposed application permissions, not standard MCP scope names.

Apply the same authorization predicate to listing, reading, search candidates and mutations. Otherwise a caller denied a document might still learn its title, an excerpt or its existence through search counts. Bind pagination cursors and caches to the effective caller and authorization context. Recheck grants before returning cached memory after a role change.

In PostgreSQL, row security can restrict visible and modifiable rows, and enabling it without an applicable policy defaults to denial. PostgreSQL also documents that superusers, roles with BYPASSRLS and normally table owners bypass it. Use a runtime role subject to the policies and test personal and shared records independently.

A read-only agent must also fail when it calls the merge operation directly. Removing write tools from discovery is useful interface behavior, but the mutation handler must enforce the grant. The distinction matters for personal versus team memory.

Budget search results before the model sees them

Make the response budget explicit: maximum hits, excerpt bytes, total serialized bytes and execution deadline. For an initial implementation, try eight hits with 500-byte excerpts under an 8 KB response ceiling, then tune against real prompts. These are suggested starting limits, not protocol requirements or measured optimal values. Count the envelope and metadata too, and truncate at valid text boundaries.

PostgreSQL provides full-text query parsing and ranking without an embedding model. This proposed query assumes a memories table, an indexed search_vector built from title and body, and row policies enforcing the caller's visibility:

WITH q AS (
  SELECT websearch_to_tsquery('english', $1) AS query
)
SELECT m.id, m.path, m.revision,
       ts_rank_cd(m.search_vector, q.query) AS rank
FROM memories AS m CROSS JOIN q
WHERE m.workspace_id = $2
  AND m.audience = $3
  AND m.search_vector @@ q.query
ORDER BY rank DESC, m.id
LIMIT 8;

Bind the query text as data, derive the workspace from authentication, and validate the requested audience against grants. Fetch bounded excerpts only for these authorized hits. The SQL limits rows; the response formatter must enforce bytes.

Keep an empty match distinct from timeout or backend failure. An optional recall hook can continue without memory after its deadline, while an explicit tool call reports the failure. Never cache a timeout as evidence that no memories exist.

Make writes conditional and retries predictable

Two agents can read revision 12 of a release note and propose different replacements. Require an expected revision for replacement and deletion. Assuming a unique record identifier and enforced row policies, a proposed PostgreSQL update is:

UPDATE memories
SET body = $1, revision = revision + 1
WHERE id = $2
  AND workspace_id = $3
  AND revision = $4
RETURNING id, path, revision;

PostgreSQL documents conditional updates and RETURNING. Under Read Committed, it rechecks the condition after a competing updater commits. If every writer increments the revision, a stale replacement will not silently overwrite that newer revision. An empty result needs application handling: report a missing or conflicting record only within the caller's authorized view.

Give merge writes an idempotency key scoped to caller and target. Record the key and committed result atomically with the memory change. A retry after a lost response should return the earlier result. A model-assisted merge should read a revision and commit conditionally too; generated prose does not resolve concurrent edits.

Record writer identity, timestamp, source reference and revision for each successful change. Keep recalled material labelled as recorded data. A sentence inside a memory asking for broader access must not become authorization to change its own permissions.

How SPYN3 exposes scoped context

SPYN3 serves context through one MCP endpoint per workspace. An admin enables Memory & skills, which is off by default. Paths distinguish spyn3://me/memories/… from spyn3://workspace/memories/…. Its tools include context_list, context_read, context_search, context_write, context_delete and remember. Reading requires call:tools; memory writes require the separate write:memory permission. Search defaults to PostgreSQL full-text search. Every memory write is audit-logged, and recalled memory is labelled as recorded data, not instructions.

Prove the boundaries with competing callers

Before connecting a second agent, exercise the contract with two people, two workspaces and a service identity. Check behavior through the same MCP tools the agents will use.

  • A caller cannot discover another person's private path through listing, search excerpts, counts or cursors.
  • A read-only key cannot replace, merge or delete memory, even with a valid record identifier.
  • Two replacements using the same revision produce one committed change and one recoverable conflict.
  • Repeating a committed merge with its idempotency key does not create another memory or revision.
  • An oversized match set stays within the response budget; a deadline produces an observable failure.

Inspect both returned content and stored revisions. A successful search is insufficient evidence if a denied write still changed the record. Use the resulting byte counts and recall latency to set session-start context budgets before loading this server into every agent.

Built for the next step

Keep context with the user, not the agent

Memory and skills behind the same endpoint as your tools, readable by any agent you connect.

Read the developer guide
About SPYN3

SPYN3 is a consolidated MCP layer: one hosted endpoint in front of every system you connect — MCP servers, Postgres, REST APIs, Google Drive — with tools foldered by source and read/write permissions per key. Switching agents means moving one URL. Free plan available; current capabilities are documented on the product facts page.

Sources

  1. MCP specification: tools — tool discovery, invocation and security considerations. Verified 27 Oct 2026
  2. MCP specification: Resources — URI-identified context, application-driven resource use and custom URI schemes. Verified 27 Oct 2026
  3. MCP specification: authorization — OAuth-based authorization for HTTP MCP servers. Verified 27 Oct 2026
  4. PostgreSQL documentation: Row Security Policies — Row visibility and mutation policies, default denial and roles that bypass row security. Verified 27 Oct 2026
  5. PostgreSQL documentation: Controlling Text Search — Full-text query parsing with websearch_to_tsquery and relevance ranking with ts_rank_cd. Verified 27 Oct 2026
  6. PostgreSQL documentation: UPDATE — Conditional UPDATE statements and RETURNING values from updated rows. Verified 27 Oct 2026
  7. PostgreSQL documentation: Transaction Isolation — Read Committed updates re-evaluate WHERE conditions after competing updates commit. Verified 27 Oct 2026